diff --git a/docker-compose.yaml b/docker-compose.yaml index 8b2aad5bbd..8b0ac7b293 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -86,7 +86,7 @@ services: api: image: ${IMAGE_REPO:-lagoon}/api:${IMAGE_REPO_API_TAG:-${IMAGE_REPO_TAG:-latest}} environment: - - KEYCLOAK_FRONTEND_URL=http://localhost:8088/ + - KEYCLOAK_FRONTEND_URL=http://localhost:8088/auth - NODE_ENV=development - OPENSEARCH_INTEGRATION_ENABLED=false - DISABLE_CORE_HARBOR=true @@ -97,6 +97,8 @@ services: - S3_BAAS_SECRET_ACCESS_KEY=minio123 - CONSOLE_LOGGING_LEVEL=debug - SIDECAR_HANDLER_HOST=apisidecarhandler + - SSH_TOKEN_ENDPOINT=localhost + - SSH_TOKEN_ENDPOINT_PORT=2020 depends_on: api-lagoon-migrations: condition: service_started diff --git a/docs/interacting/rbac.md b/docs/interacting/rbac.md index 6ea611e70d..785898ab03 100644 --- a/docs/interacting/rbac.md +++ b/docs/interacting/rbac.md @@ -8,13 +8,24 @@ When assigning a user to a group, you need to provide a group role for that user ### Platform-Wide Roles -#### Platform-Wide Admin - -The platform-wide admin has access to everything across all of Lagoon. That includes dangerous mutations like deleting all projects. Use very, _very,_ **very** carefully. +Platform-wide roles are typically assigned to people that manage Lagoon. #### Platform-Wide Owner -The platform-wide owner has access to every Lagoon group, like the group owner role, and can be used if you need a user that needs access to everything but you don't want to assign the user to every group. +The platform-wide owner has access to everything across all of Lagoon. + +#### Platform-Wide Viewer + +Similar to the platform-wide owner, except this role can only view. + +#### Platform-Wide Organization Owner + +The platform-wide organization owner role provides permission to create, update, delete, and all other permissions related to changes withing an organization, including existing organizations. It does not grant full platform-wide owner access, this means the ability to access and deploy projects still needs to be granted via a group within an organization. + +This role also has the ability to view all the deploytargets (kubernetes clusters) assigned to Lagoon so that they can be assigned to an organization when it is being created. + +!!! Warning "NOTE" + By default this role does not allow the creation of environments or the ability to trigger deployments within a project within an organization. They can add themselves to a group with a role that does grant them this permission. ### Organization Roles diff --git a/local-dev/api-data-watcher-pusher/api-data/01-populate-api-data-lagoon-demo.gql b/local-dev/api-data-watcher-pusher/api-data/01-populate-api-data-lagoon-demo.gql index 42cef18437..f3df3f192e 100644 --- a/local-dev/api-data-watcher-pusher/api-data/01-populate-api-data-lagoon-demo.gql +++ b/local-dev/api-data-watcher-pusher/api-data/01-populate-api-data-lagoon-demo.gql @@ -674,6 +674,48 @@ mutation PopulateApi { id } + UserExamplePlatformOrgOwner: addUser( + input: { + email: "platformorgowner@example.com" + comment: "platform organization owner user" + } + ) { + id + } + + ## Assign platform owner role + UserExamplePlatformOwnerRole: addPlatformRoleToUser( + user:{ + email:"platformowner@example.com" + } + role: OWNER + ){ + email + platformRoles + } + + ## Assign platform viewer role + UserExamplePlatformViewerRole: addPlatformRoleToUser( + user:{ + email:"platformviewer@example.com" + } + role: VIEWER + ){ + email + platformRoles + } + + ## Assign platform owner role + UserExamplePlatformOrgOwnerRole: addPlatformRoleToUser( + user:{ + email:"platformorgowner@example.com" + } + role: ORGANIZATIONOWNER + ){ + email + platformRoles + } + LagoonDemoGroup: addGroup( input: { name: "lagoon-demo-group" diff --git a/local-dev/k3d-seed-data/00-populate-kubernetes.gql b/local-dev/k3d-seed-data/00-populate-kubernetes.gql index fd66458724..c67eb7e896 100644 --- a/local-dev/k3d-seed-data/00-populate-kubernetes.gql +++ b/local-dev/k3d-seed-data/00-populate-kubernetes.gql @@ -79,6 +79,48 @@ mutation PopulateApi { id } + UserExamplePlatformOrgOwner: addUser( + input: { + email: "platformorgowner@example.com" + comment: "platform organization owner user" + } + ) { + id + } + + ## Assign platform owner role + UserExamplePlatformOwnerRole: addPlatformRoleToUser( + user:{ + email:"platformowner@example.com" + } + role: OWNER + ){ + email + platformRoles + } + + ## Assign platform viewer role + UserExamplePlatformViewerRole: addPlatformRoleToUser( + user:{ + email:"platformviewer@example.com" + } + role: VIEWER + ){ + email + platformRoles + } + + ## Assign platform organization owner role + UserExamplePlatformOrgOwnerRole: addPlatformRoleToUser( + user:{ + email:"platformorgowner@example.com" + } + role: ORGANIZATIONOWNER + ){ + email + platformRoles + } + LagoonDemoGroup: addGroup( input: { name: "lagoon-demo-group" diff --git a/local-dev/k3d-seed-data/seed-users.sh b/local-dev/k3d-seed-data/seed-users.sh index 1db2bcf3b8..88924d13bb 100644 --- a/local-dev/k3d-seed-data/seed-users.sh +++ b/local-dev/k3d-seed-data/seed-users.sh @@ -10,7 +10,8 @@ function is_keycloak_running { function configure_user_passwords { LAGOON_DEMO_USERS=("guest@example.com" "reporter@example.com" "developer@example.com" "maintainer@example.com" "owner@example.com") - LAGOON_DEMO_ORG_USERS=("orguser@example.com" "orgviewer@example.com" "orgadmin@example.com" "orgowner@example.com" "platformviewer@example.com" "platformowner@example.com") + LAGOON_DEMO_ORG_USERS=("orguser@example.com" "orgviewer@example.com" "orgadmin@example.com" "orgowner@example.com") + LAGOON_DEMO_PLATFORM_USERS=("platformorgowner@example.com" "platformviewer@example.com" "platformowner@example.com") for i in ${LAGOON_DEMO_USERS[@]} do @@ -23,16 +24,12 @@ function configure_user_passwords { echo Configuring password for $i /opt/keycloak/bin/kcadm.sh set-password --config $CONFIG_PATH --username $i -p $i --target-realm lagoon done -} - -function configure_platformowner { - echo Configuring platform owner role - /opt/keycloak/bin/kcadm.sh add-roles --uusername platformowner@example.com --rolename platform-owner --config $CONFIG_PATH --target-realm lagoon -} -function configure_platformviewer { - echo Configuring platform viewer role - /opt/keycloak/bin/kcadm.sh add-roles --uusername platformviewer@example.com --rolename platform-viewer --config $CONFIG_PATH --target-realm lagoon + for i in ${LAGOON_DEMO_PLATFORM_USERS[@]} + do + echo Configuring password for $i + /opt/keycloak/bin/kcadm.sh set-password --config $CONFIG_PATH --username $i -p $i --target-realm lagoon + done } function configure_keycloak { @@ -49,8 +46,6 @@ function configure_keycloak { /opt/keycloak/bin/kcadm.sh config credentials --config $CONFIG_PATH --server http://localhost:8080/auth --user $KEYCLOAK_ADMIN_USER --password $KEYCLOAK_ADMIN_PASSWORD --realm master configure_user_passwords - configure_platformowner - configure_platformviewer echo "Config of Keycloak users done" } diff --git a/services/api/src/models/user.ts b/services/api/src/models/user.ts index a0b21b296f..ce316cca0a 100644 --- a/services/api/src/models/user.ts +++ b/services/api/src/models/user.ts @@ -26,6 +26,7 @@ export interface User { owner?: boolean; admin?: boolean; organizationRole?: string; + platformRoles?: [string]; } interface UserEdit { @@ -42,6 +43,7 @@ interface UserEdit { export interface UserModel { loadAllUsers: () => Promise; + loadAllPlatformUsers: () => Promise; loadUserById: (id: string) => Promise; loadUserByUsername: (email: string) => Promise; loadUserByIdOrUsername: (userInput: UserEdit) => Promise; @@ -55,6 +57,8 @@ export interface UserModel { userGroups: Group[] ) => Promise; addUser: (userInput: User, resetPassword?: Boolean) => Promise; + addPlatformRoleToUser: (userInput: User, role: string) => Promise; + removePlatformRoleFromUser: (userInput: User, role: string) => Promise; updateUser: (userInput: UserEdit) => Promise; deleteUser: (id: string) => Promise; resetUserPassword: (id: string) => Promise; @@ -165,7 +169,7 @@ export const User = (clients: { (keycloakUser: UserRepresentation): User => // @ts-ignore R.pipe( - R.pick(['id', 'email', 'username', 'firstName', 'lastName', 'attributes', 'admin', 'owner', 'organizationRole']), + R.pick(['id', 'email', 'username', 'firstName', 'lastName', 'attributes', 'admin', 'owner', 'organizationRole', 'platformRoles']), // @ts-ignore R.set(commentLens, R.view(attrCommentLens, keycloakUser)) )(keycloakUser) @@ -348,6 +352,102 @@ export const User = (clients: { return users; }; + const loadAllPlatformUsers = async (): Promise => { + let platformUsers = []; + const keycloakPlatformOwners = await keycloakAdminClient.roles.findUsersWithRole({ + name: "platform-owner", + max: -1 + }); + for (const f1 in keycloakPlatformOwners) { + keycloakPlatformOwners[f1].platformRoles = [] + const found = platformUsers.findIndex(el => el.email === keycloakPlatformOwners[f1].email); + if (found === -1) { + keycloakPlatformOwners[f1].platformRoles.push("owner") + platformUsers.push(keycloakPlatformOwners[f1]) + } else { + platformUsers[found].platformRoles.push("owner") + } + } + const keycloakPlatformViewers = await keycloakAdminClient.roles.findUsersWithRole({ + name: "platform-viewer", + max: -1 + }); + for (const f1 in keycloakPlatformViewers) { + keycloakPlatformViewers[f1].platformRoles = [] + const found = platformUsers.findIndex(el => el.email === keycloakPlatformViewers[f1].email); + if (found === -1) { + keycloakPlatformViewers[f1].platformRoles.push("viewer") + platformUsers.push(keycloakPlatformViewers[f1]) + } else { + platformUsers[found].platformRoles.push("viewer") + } + } + const keycloakPlatformOrgOwners = await keycloakAdminClient.roles.findUsersWithRole({ + name: "platform-organization-owner", + max: -1 + }); + for (const f1 in keycloakPlatformOrgOwners) { + keycloakPlatformOrgOwners[f1].platformRoles = [] + const found = platformUsers.findIndex(el => el.email === keycloakPlatformOrgOwners[f1].email); + if (found === -1) { + keycloakPlatformOrgOwners[f1].platformRoles.push("organizationowner") + platformUsers.push(keycloakPlatformOrgOwners[f1]) + } else { + platformUsers[found].platformRoles.push("organizationowner") + } + } + const users = await transformKeycloakUsers(platformUsers); + return users; + }; + + const platformRoleSwitch = (role) => { + let computedRole = "platform-viewer" + switch (role) { + case "owner": + computedRole = "platform-owner" + break; + case "viewer": + computedRole = "platform-viewer" + break; + case "organizationowner": + computedRole = "platform-organization-owner" + break; + default: + break; + } + return computedRole + } + + const addPlatformRoleToUser = async (userInput: User, role: string): Promise => { + let computedRole = platformRoleSwitch(role) + const kcRole = await keycloakAdminClient.roles.findOneByName({ + name: computedRole + }); + let keycloakUser = await keycloakAdminClient.users.addRealmRoleMappings({ + id: userInput.id, + roles: [{ + id: kcRole.id, + name: kcRole.name + }] + }); + return keycloakUser + } + + const removePlatformRoleFromUser = async (userInput: User, role: string): Promise => { + let computedRole = platformRoleSwitch(role) + const kcRole = await keycloakAdminClient.roles.findOneByName({ + name: computedRole + }); + const keycloakUser = await keycloakAdminClient.users.delRealmRoleMappings({ + id: userInput.id, + roles: [{ + id: kcRole.id, + name: kcRole.name + }] + }); + return keycloakUser + } + const getAllGroupsForUser = async ( userId: string, organization?: number, @@ -707,6 +807,7 @@ export const User = (clients: { return { loadAllUsers, + loadAllPlatformUsers, loadUserById, loadUserByUsername, loadUserByIdOrUsername, @@ -716,6 +817,8 @@ export const User = (clients: { getAllProjectsIdsForUser, getUserRolesForProject, addUser, + addPlatformRoleToUser, + removePlatformRoleFromUser, updateUser, userLastAccessed, deleteUser, diff --git a/services/api/src/resolvers.js b/services/api/src/resolvers.js index f27b71fb0a..e39a000257 100644 --- a/services/api/src/resolvers.js +++ b/services/api/src/resolvers.js @@ -212,6 +212,9 @@ const { deleteUser, getAllUsers, getUserByEmail, + getAllPlatformUsers, + addPlatformRoleToUser, + removePlatformRoleFromUser, } = require('./resources/user/resolvers'); const { @@ -376,6 +379,11 @@ const resolvers = { ACTIVE: 'active', SUCCEEDED: 'succeeded', }, + PlatformRole: { + OWNER: 'owner', + VIEWER: 'viewer', + ORGANIZATIONOWNER: 'organizationowner', + }, Openshift: { projectUser: getProjectUser, token: getToken, @@ -581,7 +589,8 @@ const resolvers = { getGroupProjectOrganizationAssociation, getProjectGroupOrganizationAssociation, getEnvVariablesByProjectEnvironmentName, - checkBulkImportProjectsAndGroupsToOrganization + checkBulkImportProjectsAndGroupsToOrganization, + allPlatformUsers: getAllPlatformUsers, }, Mutation: { addProblem, @@ -705,7 +714,9 @@ const resolvers = { removeUserFromOrganizationGroups, bulkImportProjectsAndGroupsToOrganization, addOrUpdateEnvironmentService, - deleteEnvironmentService + deleteEnvironmentService, + addPlatformRoleToUser, + removePlatformRoleFromUser, }, Subscription: { backupChanged: backupSubscriber, diff --git a/services/api/src/resources/environment/resolvers.ts b/services/api/src/resources/environment/resolvers.ts index 202abfebd6..439156a846 100644 --- a/services/api/src/resources/environment/resolvers.ts +++ b/services/api/src/resources/environment/resolvers.ts @@ -260,24 +260,21 @@ export const getEnvironmentByKubernetesNamespaceName: ResolverFn = async ( export const getEnvironmentsByKubernetes: ResolverFn = async ( _, { kubernetes, order, createdAfter, type }, - { sqlClientPool, hasPermission, models, keycloakGrant, keycloakUsersGroups } + { sqlClientPool, hasPermission, models, keycloakGrant, keycloakUsersGroups, adminScopes } ) => { const openshift = await openshiftHelpers( sqlClientPool ).getOpenshiftByOpenshiftInput(kubernetes); let userProjectIds: number[]; - try { - await hasPermission('openshift', 'viewAll'); - } catch (err) { - if (!keycloakGrant) { - logger.warn('No grant available for getEnvironmentsByKubernetes'); - return []; - } - - // Only return projects the user can view + // if user is not platform owner or viewer, check the project ids the user has access to + if (!adminScopes.platformOwner && !adminScopes.platformViewer) { const userProjectRoles = await models.UserModel.getAllProjectsIdsForUser(keycloakGrant.access_token.content.sub, keycloakUsersGroups); userProjectIds = getUserProjectIdsFromRoleProjectIds(userProjectRoles); + if (userProjectIds.length == 0) { + // return an empty result if the user has no project ids + return []; + } } let queryBuilder = knex('environment').where('openshift', openshift.id); diff --git a/services/api/src/resources/user/resolvers.ts b/services/api/src/resources/user/resolvers.ts index 9f205dd2d8..6b99fdfd02 100644 --- a/services/api/src/resources/user/resolvers.ts +++ b/services/api/src/resources/user/resolvers.ts @@ -440,3 +440,122 @@ export const removeAdminFromOrganization: ResolverFn = async ( return organizationData; }; + +// query to list all platform users +export const getAllPlatformUsers: ResolverFn = async ( + _root, + { id, email, gitlabId, role }, + { models, adminScopes }, +) => { + // if user is platform owner or viewer + if (adminScopes.platformOwner || adminScopes.platformViewer) { + const users = await models.UserModel.loadAllPlatformUsers(); + if (id) { + const filteredById = users.filter(function (item) { + return item.id === id; + }); + return filteredById; + } + if (email) { + const filteredByEmail = users.filter(function (item) { + return item.email === email; + }); + return filteredByEmail; + } + if (gitlabId) { + const filteredByGitlab = users.filter(function (item) { + return item.gitlabId === gitlabId; + }); + return filteredByGitlab; + } + if (role) { + const filteredByPlatformRole = users.filter(function (item) { + const found = item.platformRoles.some(el => el === role); + if (found) { + return item; + } + }); + return filteredByPlatformRole; + } + return users; + } else { + throw new Error( + `Unauthorized: You don't have permission to perform this action` + ); + } +}; + +// addPlatformRoleToUser is used to add platform-owner or platform-viewer to a user +export const addPlatformRoleToUser: ResolverFn = async ( + _root, + { user: userInput, role }, + { models, userActivityLogger, adminScopes }, +) => { + // if user is platform owner + if (adminScopes.platformOwner) { + const user = await models.UserModel.loadUserByIdOrUsername({ + id: R.prop('id', userInput), + email: R.prop('email', userInput), + }); + await models.UserModel.addPlatformRoleToUser(user, role); + const users = await models.UserModel.loadAllPlatformUsers(); + const filteredByEmail = users.filter(function (item) { + return item.email === user.email; + }); + userActivityLogger(`User added a platform role to user '${user.email}'`, { + project: '', + event: 'api:addPlatformRoleToUser', + payload: { + user: { + id: user.id, + email: user.email, + role: role, + }, + } + }); + return filteredByEmail[0]; + } else { + throw new Error( + `Unauthorized: You don't have permission to perform this action` + ); + } +}; + +// removePlatformRoleFromUser will remove a platform role from a user +export const removePlatformRoleFromUser: ResolverFn = async ( + _root, + { user: userInput, role }, + { models, userActivityLogger, adminScopes }, +) => { + // if user is platform owner + if (adminScopes.platformOwner) { + const user = await models.UserModel.loadUserByIdOrUsername({ + id: R.prop('id', userInput), + email: R.prop('email', userInput), + }); + await models.UserModel.removePlatformRoleFromUser(user, role); + const users = await models.UserModel.loadAllPlatformUsers(); + const filteredByEmail = users.filter(function (item) { + return item.email === user.email; + }); + userActivityLogger(`User removed platform role from user '${user.email}'`, { + project: '', + event: 'api:removePlatformRoleFromUser', + payload: { + user: { + id: user.id, + email: user.email, + role: role, + }, + } + }); + if (filteredByEmail[0]) { + return filteredByEmail[0] + } + return user; + } else { + throw new Error( + `Unauthorized: You don't have permission to perform this action` + ); + } +}; \ No newline at end of file diff --git a/services/api/src/typeDefs.js b/services/api/src/typeDefs.js index ffa8e98719..345a085d93 100644 --- a/services/api/src/typeDefs.js +++ b/services/api/src/typeDefs.js @@ -464,6 +464,22 @@ const typeDefs = gql` # lastAccessed: String } + type PlatformUser { + id: String + email: String + firstName: String + lastName: String + comment: String + gitlabId: Int + platformRoles: [PlatformRole] + } + + enum PlatformRole { + VIEWER + OWNER + ORGANIZATIONOWNER + } + type GroupMembership { user: User role: GroupRole @@ -1440,6 +1456,7 @@ const typeDefs = gql` getProjectGroupOrganizationAssociation(input: ProjectOrgGroupsInput!): String @deprecated(reason: "Use checkBulkImportProjectsAndGroupsToOrganization instead") getEnvVariablesByProjectEnvironmentName(input: EnvVariableByProjectEnvironmentNameInput!): [EnvKeyValue] checkBulkImportProjectsAndGroupsToOrganization(input: AddProjectToOrganizationInput!): ProjectGroupsToOrganization + allPlatformUsers(id: String, email: String, gitlabId: Int, role: PlatformRole): [PlatformUser] } type ProjectGroupsToOrganization { @@ -2547,6 +2564,8 @@ const typeDefs = gql` bulkImportProjectsAndGroupsToOrganization(input: AddProjectToOrganizationInput, detachNotification: Boolean): ProjectGroupsToOrganization addOrUpdateEnvironmentService(input: AddEnvironmentServiceInput!): EnvironmentService deleteEnvironmentService(input: DeleteEnvironmentServiceInput!): String + addPlatformRoleToUser(user: UserInput!, role: PlatformRole!): PlatformUser + removePlatformRoleFromUser(user: UserInput!, role: PlatformRole!): PlatformUser } type Subscription { diff --git a/services/keycloak/javascript/META-INF/keycloak-scripts.json b/services/keycloak/javascript/META-INF/keycloak-scripts.json index c082fa7a97..1e0558b3ac 100644 --- a/services/keycloak/javascript/META-INF/keycloak-scripts.json +++ b/services/keycloak/javascript/META-INF/keycloak-scripts.json @@ -22,6 +22,11 @@ "description": "Checks the users role for the realm is Platform Viewer or higher", "fileName": "policies/users-role-for-realm-is-platform-viewer.js" }, + { + "name": "[Lagoon] Users role for realm is Platform Organization Owner", + "description": "Checks the users role for the realm is Platform Organization Owner or higher", + "fileName": "policies/users-role-for-realm-is-platform-organization-owner.js" + }, { "name": "[Lagoon] Users role for realm is Admin", "description": "Checks the users role for the realm is Admin", diff --git a/services/keycloak/javascript/policies/users-role-for-realm-is-platform-organization-owner.js b/services/keycloak/javascript/policies/users-role-for-realm-is-platform-organization-owner.js new file mode 100644 index 0000000000..491d8cbacb --- /dev/null +++ b/services/keycloak/javascript/policies/users-role-for-realm-is-platform-organization-owner.js @@ -0,0 +1,15 @@ +var realm = $evaluation.getRealm(); +var ctx = $evaluation.getContext(); +var ctxAttr = ctx.getAttributes(); + +if (!ctxAttr.exists('currentUser')) { + $evaluation.deny(); +} else { + var currentUser = ctxAttr.getValue('currentUser').asString(0); + + if (realm.isUserInRealmRole(currentUser, 'platform-organization-owner')) { + $evaluation.grant(); + } else { + $evaluation.deny(); + } +} \ No newline at end of file diff --git a/services/keycloak/lagoon-realm-base-import.json b/services/keycloak/lagoon-realm-base-import.json index dd2001cdcd..7b39a203e0 100644 --- a/services/keycloak/lagoon-realm-base-import.json +++ b/services/keycloak/lagoon-realm-base-import.json @@ -89,6 +89,12 @@ "clientRole": false, "attributes": {} }, + { + "name": "platform-organization-owner", + "composite": false, + "clientRole": false, + "attributes": {} + }, { "name": "reporter", "composite": true, @@ -1454,6 +1460,14 @@ "decisionStrategy": "UNANIMOUS", "config": {} }, + { + "name": "[Lagoon] Users role for realm is Platform Organization Owner", + "description": "Checks the users role for the realm is Platform Organization Owner or higher", + "type": "script-policies/users-role-for-realm-is-platform-organization-owner.js", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": {} + }, { "name": "[Lagoon] Users role for group is Developer", "description": "Checks the users role for a group is Developer or higher", @@ -1758,7 +1772,7 @@ "config": { "resources": "[\"organization\"]", "scopes": "[\"updateOrganization\"]", - "applyPolicies": "[\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Owner\"]" + "applyPolicies": "[\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Owner\"]" } }, { @@ -1791,7 +1805,7 @@ "config": { "resources": "[\"organization\"]", "scopes": "[\"view\",\"viewProject\",\"viewGroup\",\"viewNotification\",\"viewUser\",\"viewUsers\"]", - "applyPolicies": "[\"[Lagoon] User is admin of organization\",\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Viewer\",\"[Lagoon] Users role for realm is Platform Owner\",\"[Lagoon] User is viewer of organization\"]" + "applyPolicies": "[\"[Lagoon] User is admin of organization\",\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Viewer\",\"[Lagoon] Users role for realm is Platform Owner\",\"[Lagoon] User is viewer of organization\"]" } }, { @@ -1864,11 +1878,11 @@ "name": "Platform Owner Manage Organizations and Owners", "type": "scope", "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", + "decisionStrategy": "AFFIRMATIVE", "config": { "resources": "[\"organization\"]", "scopes": "[\"delete\",\"update\",\"add\"]", - "applyPolicies": "[\"[Lagoon] Users role for realm is Platform Owner\"]" + "applyPolicies": "[\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Owner\"]" } }, { @@ -1923,7 +1937,7 @@ "config": { "resources": "[\"organization\"]", "scopes": "[\"addViewer\",\"addOwner\"]", - "applyPolicies": "[\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Owner\"]" + "applyPolicies": "[\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Owner\"]" } }, { @@ -1934,7 +1948,7 @@ "config": { "resources": "[\"organization\"]", "scopes": "[\"addProject\",\"updateProject\",\"deleteProject\"]", - "applyPolicies": "[\"[Lagoon] User is admin of organization\",\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Owner\"]" + "applyPolicies": "[\"[Lagoon] User is admin of organization\",\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Owner\"]" } }, { @@ -1945,7 +1959,7 @@ "config": { "resources": "[\"organization\"]", "scopes": "[\"removeGroup\",\"addGroup\"]", - "applyPolicies": "[\"[Lagoon] User is admin of organization\",\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Owner\"]" + "applyPolicies": "[\"[Lagoon] User is admin of organization\",\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Owner\"]" } }, { @@ -1956,7 +1970,7 @@ "config": { "resources": "[\"organization\"]", "scopes": "[\"addNotification\",\"removeNotification\",\"updateNotification\"]", - "applyPolicies": "[\"[Lagoon] User is admin of organization\",\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Owner\"]" + "applyPolicies": "[\"[Lagoon] User is admin of organization\",\"[Lagoon] User is owner of organization\",\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Owner\"]" } }, { @@ -1989,7 +2003,7 @@ "config": { "resources": "[\"organization\"]", "scopes": "[\"viewAll\"]", - "applyPolicies": "[\"[Lagoon] Users role for realm is Platform Viewer\",\"[Lagoon] Users role for realm is Platform Owner\"]" + "applyPolicies": "[\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Viewer\",\"[Lagoon] Users role for realm is Platform Owner\"]" } }, { @@ -2363,7 +2377,7 @@ "config": { "resources": "[\"openshift\"]", "scopes": "[\"viewAll\"]", - "applyPolicies": "[\"[Lagoon] Users role for realm is Platform Viewer\",\"[Lagoon] Users role for realm is Platform Owner\"]" + "applyPolicies": "[\"[Lagoon] Users role for realm is Platform Organization Owner\",\"[Lagoon] Users role for realm is Platform Viewer\",\"[Lagoon] Users role for realm is Platform Owner\"]" } }, { diff --git a/services/keycloak/startup-scripts/00-configure-lagoon.sh b/services/keycloak/startup-scripts/00-configure-lagoon.sh index b10e2b6cd1..5f25941797 100755 --- a/services/keycloak/startup-scripts/00-configure-lagoon.sh +++ b/services/keycloak/startup-scripts/00-configure-lagoon.sh @@ -674,6 +674,184 @@ function add_lagoon-ui-oidc_client { echo '{"protocol":"openid-connect","config":{"id.token.claim":"true","access.token.claim":"true","userinfo.token.claim":"true","user.attribute":"lagoon-uid","claim.name":"lagoon.user_id","jsonType.label":"int","multivalued":""},"name":"Lagoon User ID","protocolMapper":"oidc-usermodel-attribute-mapper"}' | /opt/keycloak/bin/kcadm.sh create -r ${KEYCLOAK_REALM:-master} clients/$CLIENT_ID/protocol-mappers/models --config $CONFIG_PATH -f - } +function add_update_platform_organization_permissions { + # The changes here match the changes that are made in the realm import script + # fresh installs will not need to perform this migration as the changes will already be in the import + # this will only run on existing installations to get it into a state that matches the realm import + CLIENT_ID=$(/opt/keycloak/bin/kcadm.sh get -r lagoon clients?clientId=api --config $CONFIG_PATH | jq -r '.[0]["id"]') + platform_organization_owner_permission=$(/opt/keycloak/bin/kcadm.sh get -r lagoon clients/$CLIENT_ID/authz/resource-server/permission?name=View+All+Organizations --config $CONFIG_PATH | jq -r '.[0]["id"]') + associated_policies=$(/opt/keycloak/bin/kcadm.sh get -r lagoon clients/$CLIENT_ID/authz/resource-server/policy/$platform_organization_owner_permission/associatedPolicies --config $CONFIG_PATH | jq -c 'map({name})') + + # check the permission to see if the platform organization owner role is already configured + if [[ "$associated_policies" =~ 'Users role for realm is Platform Organization Owner' ]]; then + echo "add_update_platform_organization_permissions already configured" + return 0 + fi + + echo Creating platform organization owner js mapper policy + local p_name1="Users role for realm is Platform Organization Owner" + local script_name1="[Lagoon] $p_name1" + local script_type1="script-policies/$(echo $p_name1 | sed -e 's/.*/\L&/' -e 's/ /-/g').js" + echo '{"name":"'$script_name1'","type":"'$script_type1'"}' | /opt/keycloak/bin/kcadm.sh create -r lagoon clients/$CLIENT_ID/authz/resource-server/policy/$(echo $script_type1 | sed -e 's/\//%2F/') --config $CONFIG_PATH -f - + + echo Create platform organization owner role + /opt/keycloak/bin/kcadm.sh create roles --config $CONFIG_PATH -r ${KEYCLOAK_REALM:-master} -s name=platform-organization-owner + + echo Re-configuring organization:updateOrganization + #Delete existing permissions + update_organization=$(/opt/keycloak/bin/kcadm.sh get -r lagoon clients/$CLIENT_ID/authz/resource-server/permission?name=Update+Organization --config $CONFIG_PATH | jq -r '.[0]["id"]') + /opt/keycloak/bin/kcadm.sh delete -r lagoon clients/$CLIENT_ID/authz/resource-server/permission/$update_organization --config $CONFIG_PATH + + /opt/keycloak/bin/kcadm.sh create clients/$CLIENT_ID/authz/resource-server/permission/scope --config $CONFIG_PATH -r lagoon -f - <