Skip to content

Commit

Permalink
Merge pull request #3752 from uselagoon/platform-viewer
Browse files Browse the repository at this point in the history
feat: introduce platform-viewer role
  • Loading branch information
tobybellwood authored Jul 4, 2024
2 parents d4c656e + 8075e46 commit b8ffdf4
Show file tree
Hide file tree
Showing 27 changed files with 527 additions and 207 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,15 @@ mutation PopulateApi {
id
}

UserExamplePlatformViewer: addUser(
input: {
email: "[email protected]"
comment: "platform viewer user"
}
) {
id
}

LagoonDemoGroup: addGroup(
input: {
name: "lagoon-demo-group"
Expand Down
9 changes: 9 additions & 0 deletions local-dev/k3d-seed-data/00-populate-kubernetes.gql
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ mutation PopulateApi {
id
}

UserExamplePlatformViewer: addUser(
input: {
email: "[email protected]"
comment: "platform viewer user"
}
) {
id
}

LagoonDemoGroup: addGroup(
input: {
name: "lagoon-demo-group"
Expand Down
8 changes: 7 additions & 1 deletion local-dev/k3d-seed-data/seed-users.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function is_keycloak_running {
function configure_user_passwords {

LAGOON_DEMO_USERS=("[email protected]" "[email protected]" "[email protected]" "[email protected]" "[email protected]")
LAGOON_DEMO_ORG_USERS=("[email protected]" "[email protected]" "[email protected]" "[email protected]" "[email protected]")
LAGOON_DEMO_ORG_USERS=("[email protected]" "[email protected]" "[email protected]" "[email protected]" "[email protected]" "[email protected]")

for i in ${LAGOON_DEMO_USERS[@]}
do
Expand All @@ -30,6 +30,11 @@ function configure_platformowner {
/opt/keycloak/bin/kcadm.sh add-roles --uusername [email protected] --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 [email protected] --rolename platform-viewer --config $CONFIG_PATH --target-realm Lagoon
}

function configure_keycloak {
until is_keycloak_running; do
echo Keycloak still not running, waiting 5 seconds
Expand All @@ -45,6 +50,7 @@ function configure_keycloak {

configure_user_passwords
configure_platformowner
configure_platformviewer

echo "Config of Keycloak users done"
}
Expand Down
60 changes: 28 additions & 32 deletions services/api/src/apolloServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,22 @@ const apolloServer = new ApolloServer({
let groupRoleProjectIds = []
const keycloakGrant = grant
let legacyGrant = legacyCredentials ? legacyCredentials : null
let platformOwner = false
let platformViewer = false
if (keycloakGrant) {
// get all the users keycloak groups, do this early to reduce the number of times this is called otherwise
keycloakUsersGroups = await User.User(modelClients).getAllGroupsForUser(keycloakGrant.access_token.content.sub);
serviceAccount = await keycloakGrantManager.obtainFromClientCredentials();
currentUser = await User.User(modelClients).loadUserById(keycloakGrant.access_token.content.sub);
const userRoleMapping = await keycloakAdminClient.users.listRealmRoleMappings({id: currentUser.id})
for (const role of userRoleMapping) {
if (role.name == "platform-owner") {
platformOwner = true
}
if (role.name == "platform-viewer") {
platformViewer = true
}
}
// grab the users project ids and roles in the first request
groupRoleProjectIds = await User.User(modelClients).getAllProjectsIdsForUser(currentUser.id, keycloakUsersGroups);
}
Expand All @@ -153,6 +164,10 @@ const apolloServer = new ApolloServer({
EnvironmentModel: EnvironmentModel.EnvironmentModel(modelClients)
},
keycloakUsersGroups,
adminScopes: {
platformOwner: platformOwner,
platformViewer: platformViewer,
},
};
},
onDisconnect: (websocket, context) => {
Expand Down Expand Up @@ -192,11 +207,22 @@ const apolloServer = new ApolloServer({
let groupRoleProjectIds = []
const keycloakGrant = req.kauth ? req.kauth.grant : null
let legacyGrant = req.legacyCredentials ? req.legacyCredentials : null
let platformOwner = false
let platformViewer = false
if (keycloakGrant) {
// get all the users keycloak groups, do this early to reduce the number of times this is called otherwise
keycloakUsersGroups = await User.User(modelClients).getAllGroupsForUser(keycloakGrant.access_token.content.sub);
serviceAccount = await keycloakGrantManager.obtainFromClientCredentials();
currentUser = await User.User(modelClients).loadUserById(keycloakGrant.access_token.content.sub);
const userRoleMapping = await keycloakAdminClient.users.listRealmRoleMappings({id: currentUser.id})
for (const role of userRoleMapping) {
if (role.name == "platform-owner") {
platformOwner = true
}
if (role.name == "platform-viewer") {
platformViewer = true
}
}
// grab the users project ids and roles in the first request
groupRoleProjectIds = await User.User(modelClients).getAllProjectsIdsForUser(currentUser.id, keycloakUsersGroups);
await User.User(modelClients).userLastAccessed(currentUser);
Expand All @@ -210,34 +236,6 @@ const apolloServer = new ApolloServer({
const hasPermission = req.kauth
? keycloakHasPermission(req.kauth.grant, requestCache, modelClients, serviceAccount, currentUser, groupRoleProjectIds)
: legacyHasPermission(req.legacyCredentials)
let projectViewAll = false
let groupViewAll = false
let environmentViewAll = false
let deploytargetViewAll = false
try {
await hasPermission("project","viewAll")
projectViewAll = true
} catch(err) {
// do nothing
}
try {
await hasPermission("group","viewAll")
groupViewAll = true
} catch(err) {
// do nothing
}
try {
await hasPermission("environment","viewAll")
environmentViewAll = true
} catch(err) {
// do nothing
}
try {
await hasPermission("openshift","viewAll")
deploytargetViewAll = true
} catch(err) {
// do nothing
}

return {
keycloakAdminClient,
Expand Down Expand Up @@ -268,10 +266,8 @@ const apolloServer = new ApolloServer({
},
keycloakUsersGroups,
adminScopes: {
projectViewAll: projectViewAll,
groupViewAll: groupViewAll,
environmentViewAll: environmentViewAll,
deploytargetViewAll: deploytargetViewAll,
platformOwner: platformOwner,
platformViewer: platformViewer,
},
};
}
Expand Down
11 changes: 7 additions & 4 deletions services/api/src/clients/pubSub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const createSubscribe = (events): ResolverFn => async (
info
) => {
const { environment } = args;
const { sqlClientPool, hasPermission } = context;
const { sqlClientPool, hasPermission, adminScopes } = context;

const rows = await query(
sqlClientPool,
Expand All @@ -46,9 +46,12 @@ const createSubscribe = (events): ResolverFn => async (
const project = path([0, 'project'], rows);

try {
await hasPermission('environment', 'view', {
project
});
// if the user is not a platform owner or viewer, then perform normal permission check
if (!adminScopes.platformOwner && !adminScopes.platformViewer) {
await hasPermission('environment', 'view', {
project
});
}
} catch (err) {
throw new ForbiddenError(err.message);
}
Expand Down
3 changes: 2 additions & 1 deletion services/api/src/resources/backup/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ export const getBackupsByEnvironmentId: ResolverFn = async (
sqlClientPool
).getEnvironmentById(environmentId);

if (!adminScopes.projectViewAll) {
// if the user is not a platform owner or viewer, then perform normal permission check
if (!adminScopes.platformOwner && !adminScopes.platformViewer) {
await hasPermission('backup', 'view', {
project: environment.project
});
Expand Down
7 changes: 4 additions & 3 deletions services/api/src/resources/deployment/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const getDeploymentsByBulkId: ResolverFn = async (
export const getDeploymentsByFilter: ResolverFn = async (
root,
input,
{ sqlClientPool, hasPermission, models, keycloakGrant, keycloakUsersGroups }
{ sqlClientPool, hasPermission, models, keycloakGrant, keycloakUsersGroups, adminScopes }
) => {

const { openshifts, deploymentStatus = ["NEW", "PENDING", "RUNNING", "QUEUED"] } = input;
Expand Down Expand Up @@ -205,7 +205,8 @@ export const getDeploymentsByEnvironmentId: ResolverFn = async (
sqlClientPool
).getEnvironmentById(eid);

if (!adminScopes.projectViewAll) {
// if the user is not a platform owner or viewer, then perform normal permission check
if (!adminScopes.platformOwner && !adminScopes.platformViewer) {
await hasPermission('deployment', 'view', {
project: environment.project
});
Expand Down Expand Up @@ -1132,7 +1133,7 @@ export const deployEnvironmentPromote: ResolverFn = async (
export const switchActiveStandby: ResolverFn = async (
root,
{ input: { project: projectInput } },
{ sqlClientPool, hasPermission, keycloakGrant, legacyGrant }
{ sqlClientPool, hasPermission, keycloakGrant, legacyGrant, adminScopes }
) => {
const project = await projectHelpers(sqlClientPool).getProjectByProjectInput(
projectInput
Expand Down
8 changes: 4 additions & 4 deletions services/api/src/resources/deploytargetconfig/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Helpers as organizationHelpers } from '../organization/helpers';
export const getDeployTargetConfigById = async (
root,
args,
{ sqlClientPool, hasPermission }
{ sqlClientPool, hasPermission, adminScopes }
) => {
const deployTargetConfig = await Helpers(sqlClientPool).getDeployTargetConfigById(args.id);

Expand All @@ -25,15 +25,15 @@ export const getDeployTargetConfigById = async (
// since deploytargetconfigs are associated to a project
// re-use the existing `project:view` permissions check, since the same sorts of fields
// are viewable by the same permissions at the project scope
await projectHelpers(sqlClientPool).checkOrgProjectViewPermission(hasPermission, deployTargetConfig.project)
await projectHelpers(sqlClientPool).checkOrgProjectViewPermission(hasPermission, deployTargetConfig.project, adminScopes)

return deployTargetConfig;
};

export const getDeployTargetConfigsByProjectId: ResolverFn = async (
project,
args,
{ sqlClientPool, hasPermission, keycloakGrant, models }
{ sqlClientPool, hasPermission, keycloakGrant, models, adminScopes }
) => {

let pid = args.project;
Expand All @@ -44,7 +44,7 @@ export const getDeployTargetConfigsByProjectId: ResolverFn = async (
// since deploytargetconfigs are associated to a project
// re-use the existing `project:view` permissions check, since the same sorts of fields
// are viewable by the same permissions at the project scope
await projectHelpers(sqlClientPool).checkOrgProjectViewPermission(hasPermission, pid)
await projectHelpers(sqlClientPool).checkOrgProjectViewPermission(hasPermission, pid, adminScopes)

const rows = await query(sqlClientPool, Sql.selectDeployTargetConfigsByProjectId(pid));

Expand Down
12 changes: 8 additions & 4 deletions services/api/src/resources/env-variables/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const getEnvVarsByProjectId: ResolverFn = async (
{ sqlClientPool, hasPermission, adminScopes },
info
) => {
if (!adminScopes.projectViewAll) {
// if the user is not a platform owner or viewer, then perform normal permission check
if (!adminScopes.platformOwner && !adminScopes.platformViewer) {
const index = info.fieldNodes[0].selectionSet.selections.findIndex(item => item.name.value === "value");
if (index != -1) {
await hasPermission('env_var', 'project:viewValue', {
Expand Down Expand Up @@ -56,7 +57,8 @@ export const getEnvVarsByEnvironmentId: ResolverFn = async (
sqlClientPool
).getEnvironmentById(eid)

if (!adminScopes.projectViewAll) {
// if the user is not a platform owner or viewer, then perform normal permission check
if (!adminScopes.platformOwner && !adminScopes.platformViewer) {
const index = info.fieldNodes[0].selectionSet.selections.findIndex(item => item.name.value === "value");
if (index != -1) {
await hasPermission(
Expand Down Expand Up @@ -405,7 +407,8 @@ export const getEnvVariablesByProjectEnvironmentName: ResolverFn = async (
);
const environment = environmentRows[0];

if (!adminScopes.projectViewAll) {
// if the user is not a platform owner or viewer, then perform normal permission check
if (!adminScopes.platformOwner && !adminScopes.platformViewer) {
if (index != -1) {
await hasPermission(
'env_var',
Expand Down Expand Up @@ -445,7 +448,8 @@ export const getEnvVariablesByProjectEnvironmentName: ResolverFn = async (
}
} else if (projectName) {
// is project
if (!adminScopes.projectViewAll) {
// if the user is not a platform owner or viewer, then perform normal permission check
if (!adminScopes.platformOwner && !adminScopes.platformViewer) {
if (index != -1) {
await hasPermission('env_var', 'project:viewValue', {
project: projectId
Expand Down
Loading

0 comments on commit b8ffdf4

Please sign in to comment.