From 36c1558e0df18033f550cd04fe3c3e7758906a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Thu, 28 Mar 2024 12:17:29 +0100 Subject: [PATCH 1/6] `server` Simple support to sync users from SG over to Ayon --- server/frontend/dist/index.html | 1 + server/frontend/dist/shotgrid-addon.js | 157 ++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 6 deletions(-) diff --git a/server/frontend/dist/index.html b/server/frontend/dist/index.html index c5ffd444..453bf7aa 100644 --- a/server/frontend/dist/index.html +++ b/server/frontend/dist/index.html @@ -9,6 +9,7 @@

Shotgrid Sync

+

From this page you can Import projects from Shotgrid or trigger a project Sync!
In order for the Syncronization to work, a ShotgridProcessor service must be running, this page will only create events (with the topic: "shotgrid.event") for the processor to handle. diff --git a/server/frontend/dist/shotgrid-addon.js b/server/frontend/dist/shotgrid-addon.js index fe43c4bc..f2bb32a6 100644 --- a/server/frontend/dist/shotgrid-addon.js +++ b/server/frontend/dist/shotgrid-addon.js @@ -123,6 +123,144 @@ const populateTable = async () => { } +const syncUsers = async () => { + /* Get all the Users from AYON and Shotgrid, then populate the table with their info + and a button to Synchronize if they pass the requirements */ + ayonUsers = await getAyonUsers(); + sgUsers = await getShotgridUsers(); + + sgUsers.forEach((sg_user) => { + let already_exists = false + ayonUsers.forEach((user) => { + if (sg_user.login == user.name) { + already_exists = true + } + }) + if (!already_exists) { + createNewUserInAyon(sg_user.login, sg_user.email, sg_user.name) + } + }) +} + + +const getShotgridUsers = async () => { + /* Query Shotgrid for all active users. */ + const sgBaseUrl = `${addonSettings.shotgrid_server.replace(/\/+$/, '')}/api/v1` + sgAuthToken = await axios + .post(`${sgBaseUrl}/auth/access_token`, { + client_id: addonSettings.shotgrid_script_name, + client_secret: addonSettings.shotgrid_api_key, + grant_type: "client_credentials", + }, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json' + } + }) + .then((result) => result.data.access_token) + .catch((error) => { + console.log("Unable to Acquire the Shotgrid Authorization Token!") + console.log(error) + }); + + sgUsers = await axios + .get(`${sgBaseUrl}/entity/human_users?fields=*`, { + headers: { + 'Authorization': `Bearer ${sgAuthToken}`, + 'Accept': 'application/json' + } + }) + .then((result) => result.data.data) + .catch((error) => { + console.log("Unable to Fetch Shotgrid Users!") + console.log(error) + }); + + var sgUsersConformed = [] + + users_to_ignore = ["dummy", "root", "support"] + if (sgUsers) { + sgUsers.forEach((sg_user) => { + if ( + sg_user.attributes.sg_status_list == "act" && + !users_to_ignore.some(item => sg_user.attributes.email.includes(item)) + ) { + sgUsersConformed.push({ + "login": sg_user.attributes.login, + "name": sg_user.attributes.name, + "email": sg_user.attributes.email, + }) + } + }); + } + return sgUsersConformed; +} + + +const getAyonUsers = async () => { + /* Query AYON for all existing users. */ + ayonUsers = await axios({ + url: '/graphql', + headers: {"Authorization": `Bearer ${accessToken}`}, + method: 'post', + data: { + query: ` + query ActiveUsers { + users { + edges { + node { + attrib { + email + fullName + } + active + name + } + } + } + } + ` + } + }).then((result) => result.data.data.users.edges); + + var ayonUsersConformed = [] + + if (ayonUsers) { + ayonUsers.forEach((user) => { + ayonUsersConformed.push({ + "name": user.node.name, + "email": user.node.attrib.email, + "fullName": user.node.attrib.fullName, + }) + }) + } + return ayonUsersConformed +} + + +const createNewUserInAyon = async (login, email, name) => { + /* Spawn an AYON Event of topic "shotgrid.event" to synchcronize a project + from Shotgrid into AYON. */ + call_result_paragraph = document.getElementById("call-result"); + + response = await ayonAPI + .put("/api/users/" + login, { + "active": true, + "attrib": { + "fullName": name, + "email": email, + }, + "password": login, + }) + .then((result) => result) + .catch((error) => { + console.log("Unable to create user in AYON!") + console.log(error) + call_result_paragraph.innerHTML = `Unable to create user in AYON! ${error}` + }); +} + + const getShotgridProjects = async () => { /* Query Shotgrid for all existing projects. */ const sgBaseUrl = `${addonSettings.shotgrid_server.replace(/\/+$/, '')}/api/v1` @@ -160,12 +298,19 @@ const getShotgridProjects = async () => { if (sgProjects) { sgProjects.forEach((project) => { - sgProjectsConformed.push({ - "name": project.attributes.name, - "code": project.attributes[`${addonSettings.shotgrid_project_code_field}`], - "shotgridId": project.id, - "ayonId": project.attributes.sg_ayon_id, - }) + /* Only add projects that don't contain whitespaces in the name + and have a code name as those are the requirements to sync to Ayon. */ + if ( + !project.attributes.name.includes(" ") && + project.attributes[`${addonSettings.shotgrid_project_code_field}`] + ) { + sgProjectsConformed.push({ + "name": project.attributes.name, + "code": project.attributes[`${addonSettings.shotgrid_project_code_field}`], + "shotgridId": project.id, + "ayonId": project.attributes.sg_ayon_id, + }) + } }); } return sgProjectsConformed; From ad9ebf13fd343f47a4c1b21482c6cf72b64b413c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Thu, 28 Mar 2024 12:36:15 +0100 Subject: [PATCH 2/6] `server` remove outdated docstring --- server/frontend/dist/shotgrid-addon.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/frontend/dist/shotgrid-addon.js b/server/frontend/dist/shotgrid-addon.js index f2bb32a6..62e7140c 100644 --- a/server/frontend/dist/shotgrid-addon.js +++ b/server/frontend/dist/shotgrid-addon.js @@ -239,8 +239,6 @@ const getAyonUsers = async () => { const createNewUserInAyon = async (login, email, name) => { - /* Spawn an AYON Event of topic "shotgrid.event" to synchcronize a project - from Shotgrid into AYON. */ call_result_paragraph = document.getElementById("call-result"); response = await ayonAPI From df147f734b40e81d1b644ef9e3af91c772f81fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Fri, 29 Mar 2024 16:59:30 +0100 Subject: [PATCH 3/6] Remove projects filtering from PR --- server/frontend/dist/shotgrid-addon.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/server/frontend/dist/shotgrid-addon.js b/server/frontend/dist/shotgrid-addon.js index 62e7140c..a9ede4f6 100644 --- a/server/frontend/dist/shotgrid-addon.js +++ b/server/frontend/dist/shotgrid-addon.js @@ -296,19 +296,12 @@ const getShotgridProjects = async () => { if (sgProjects) { sgProjects.forEach((project) => { - /* Only add projects that don't contain whitespaces in the name - and have a code name as those are the requirements to sync to Ayon. */ - if ( - !project.attributes.name.includes(" ") && - project.attributes[`${addonSettings.shotgrid_project_code_field}`] - ) { - sgProjectsConformed.push({ - "name": project.attributes.name, - "code": project.attributes[`${addonSettings.shotgrid_project_code_field}`], - "shotgridId": project.id, - "ayonId": project.attributes.sg_ayon_id, - }) - } + sgProjectsConformed.push({ + "name": project.attributes.name, + "code": project.attributes[`${addonSettings.shotgrid_project_code_field}`], + "shotgridId": project.id, + "ayonId": project.attributes.sg_ayon_id, + }) }); } return sgProjectsConformed; From 70af42083652182e366a030e579fc990e748258c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Fri, 29 Mar 2024 17:03:36 +0100 Subject: [PATCH 4/6] Remove indentation --- server/frontend/dist/shotgrid-addon.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/frontend/dist/shotgrid-addon.js b/server/frontend/dist/shotgrid-addon.js index a9ede4f6..e476d17c 100644 --- a/server/frontend/dist/shotgrid-addon.js +++ b/server/frontend/dist/shotgrid-addon.js @@ -296,12 +296,12 @@ const getShotgridProjects = async () => { if (sgProjects) { sgProjects.forEach((project) => { - sgProjectsConformed.push({ - "name": project.attributes.name, - "code": project.attributes[`${addonSettings.shotgrid_project_code_field}`], - "shotgridId": project.id, - "ayonId": project.attributes.sg_ayon_id, - }) + sgProjectsConformed.push({ + "name": project.attributes.name, + "code": project.attributes[`${addonSettings.shotgrid_project_code_field}`], + "shotgridId": project.id, + "ayonId": project.attributes.sg_ayon_id, + }) }); } return sgProjectsConformed; From 3c62d5f88ac4a6d7b00b57d2c7b0b6cfb13514bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Tue, 30 Jul 2024 16:53:18 +0200 Subject: [PATCH 5/6] Sync changes done in release/alkemy-x branch over --- frontend/dist/shotgrid-addon.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/dist/shotgrid-addon.js b/frontend/dist/shotgrid-addon.js index aa6b711f..9b614016 100644 --- a/frontend/dist/shotgrid-addon.js +++ b/frontend/dist/shotgrid-addon.js @@ -147,7 +147,7 @@ const getShotgridUsers = async () => { const sgBaseUrl = `${addonSettings.shotgrid_server.replace(/\/+$/, '')}/api/v1` sgAuthToken = await axios .post(`${sgBaseUrl}/auth/access_token`, { - client_id: addonSettings.shotgrid_script_name, + client_id: `${addonSettings.service_settings.script_name}`, client_secret: addonSettings.shotgrid_api_key, grant_type: "client_credentials", }, { @@ -163,7 +163,7 @@ const getShotgridUsers = async () => { }); sgUsers = await axios - .get(`${sgBaseUrl}/entity/human_users?fields=*`, { + .get(`${sgBaseUrl}/entity/human_users?filter[sg_status_list]=act&fields=login,name,email`, { headers: { 'Authorization': `Bearer ${sgAuthToken}`, 'Accept': 'application/json' @@ -175,13 +175,12 @@ const getShotgridUsers = async () => { console.log(error) }); + /* Do some extra clean up on the users returned. */ var sgUsersConformed = [] - users_to_ignore = ["dummy", "root", "support"] if (sgUsers) { sgUsers.forEach((sg_user) => { if ( - sg_user.attributes.sg_status_list == "act" && !users_to_ignore.some(item => sg_user.attributes.email.includes(item)) ) { sgUsersConformed.push({ @@ -238,6 +237,8 @@ const getAyonUsers = async () => { const createNewUserInAyon = async (login, email, name) => { + /* Spawn an AYON Event of topic "shotgrid.event" to synchcronize a project + from Shotgrid into AYON. */ call_result_paragraph = document.getElementById("call-result"); response = await ayonAPI @@ -254,7 +255,7 @@ const createNewUserInAyon = async (login, email, name) => { console.log("Unable to create user in AYON!") console.log(error) call_result_paragraph.innerHTML = `Unable to create user in AYON! ${error}` - }); + }); } From 9a1cebf009c141a30467da11f4bca9a26b969945 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Nov 2024 14:50:31 +0100 Subject: [PATCH 6/6] Update user creation and validation logic - Added user ID to the new user creation function. - Enhanced login validation by sanitizing input and ensuring it meets specific patterns. - Updated API call to use validated login format. --- frontend/dist/shotgrid-addon.js | 37 ++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/frontend/dist/shotgrid-addon.js b/frontend/dist/shotgrid-addon.js index 9b614016..d8eb3638 100644 --- a/frontend/dist/shotgrid-addon.js +++ b/frontend/dist/shotgrid-addon.js @@ -136,7 +136,8 @@ const syncUsers = async () => { } }) if (!already_exists) { - createNewUserInAyon(sg_user.login, sg_user.email, sg_user.name) + createNewUserInAyon( + sg_user.id ,sg_user.login, sg_user.email, sg_user.name) } }) } @@ -184,6 +185,7 @@ const getShotgridUsers = async () => { !users_to_ignore.some(item => sg_user.attributes.email.includes(item)) ) { sgUsersConformed.push({ + "id": sg_user.id, "login": sg_user.attributes.login, "name": sg_user.attributes.name, "email": sg_user.attributes.email, @@ -235,19 +237,48 @@ const getAyonUsers = async () => { return ayonUsersConformed } +function validateLogin(login) { + // First sanitize by replacing @ with underscore + let new_login = login.replace(/@/g, '_'); -const createNewUserInAyon = async (login, email, name) => { + // Ensure valid pattern + const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9_\.\-]*[a-zA-Z0-9]$/; + + if (!validPattern.test(new_login)) { + // If invalid, create valid string by: + // 1. Remove invalid chars + // 2. Ensure starts/ends with alphanumeric + let _new_login = new_login.replace(/[^a-zA-Z0-9_\.\-]/g, '') + .replace(/^[^a-zA-Z0-9]+/, '') + .replace(/[^a-zA-Z0-9]+$/, ''); + + // If result too short, append 'user' + if (_new_login.length < 2) { + new_login = 'user' + _new_login; + } + } + + return new_login; +} + +const createNewUserInAyon = async (id, login, email, name) => { /* Spawn an AYON Event of topic "shotgrid.event" to synchcronize a project from Shotgrid into AYON. */ call_result_paragraph = document.getElementById("call-result"); + // make sure no @ and . or - is in login string + let fixed_login = validateLogin(login); + response = await ayonAPI - .put("/api/users/" + login, { + .put("/api/users/" + fixed_login, { "active": true, "attrib": { "fullName": name, "email": email, }, + "data": { + "sg_user_id": id + }, "password": login, }) .then((result) => result)