Skip to content

Commit

Permalink
feat: user management (#118)
Browse files Browse the repository at this point in the history
* chore: release branch [ci skip] (#116)

* feat: update readme and launch&tasks.json files for keycloak operator

* feat: rm unnecessary lines from launch.json and package.json

* feat: add createTeamUser in the keycloak operator

* feat: update groups

* feat: update create user

* feat: update harbor operator

* feat: update create user

* feat: add delete keycloak users function

* test: delete keycloak users

* feat: update create keycloak user

* feat: update keycloak user without credentials

* test: update user

* feat: update keycloak createUpdateUser

* feat: update keycloak.ts

* test: users

* feat: keycloak userCreateUpdate

* feat: update user creation

* feat: update keycloak manageUsers flow

* feat: update keycloak.ts and add keycloak.test.ts

* feat: update manageUsers

* feat: update existing otomi-admin user groups

* fix: update platformAdminUser variable name

---------

Co-authored-by: Ani Argjiri <[email protected]>
  • Loading branch information
ferruhcihan and Ani1357 authored Oct 14, 2024
1 parent 2c39535 commit d086529
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 102 deletions.
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"camelcase",
"creds",
"gitea",
"keycloak",
"kubernetes",
"oidc",
"openid",
Expand Down
34 changes: 5 additions & 29 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,41 +113,17 @@
{
"type": "node",
"request": "launch",
"name": "Debug Harbor task",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "tasks:harbor-dev"],
"cwd": "${workspaceRoot}",
"console": "integratedTerminal",
"envFile": "${workspaceFolder}/.env"
// "env": {
// "NODE_EXTRA_CA_CERTS": "${workspaceFolder}/.env.ca"
// }
},
{
"type": "node",
"request": "launch",
"name": "Debug Keycloak task",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "tasks:keycloak-dev"],
"cwd": "${workspaceRoot}",
"console": "integratedTerminal",
"envFile": "${workspaceFolder}/.env",
"env": {
"NODE_EXTRA_CA_CERTS": "${workspaceFolder}/.env.ca"
}
},
{
"type": "node",
"request": "launch",
"name": "Debug Keycloak operator",
"name": "Debug keycloak operator",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "operator:keycloak-dev"],
"cwd": "${workspaceRoot}",
"console": "integratedTerminal",
"envFile": "${workspaceFolder}/.env",
"env": {
"NODE_EXTRA_CA_CERTS": "${workspaceFolder}/.env.ca"
}
"NODE_EXTRA_CA_CERTS": "${workspaceFolder}/.env.ca",
"KUBECONFIG": "/path/to/your/kubeconfig.yaml",
},
"preLaunchTask": "port-forward-keycloak"
},
{
"type": "node",
Expand Down
11 changes: 11 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@
"reveal": "always",
"panel": "new"
}
},
{
"label": "port-forward-keycloak",
"type": "shell",
"command": "export KUBECONFIG=/path/to/your/kubeconfig.yaml && kubectl -n keycloak port-forward svc/keycloak-operator 8084:80",
"problemMatcher": [],
"isBackground": true,
"presentation": {
"reveal": "always",
"panel": "new"
}
}
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Then start a proxy to the api you wish to target:

- gitea: `k -n gitea port-forward svc/gitea-http 8082:3000 &`
- harbor: `k -n harbor port-forward svc/harbor-core 8083:80 &`
- keycloak: `k -n keycloak port-forward svc/keycloak-http 8084:80 &`
- keycloak: `k -n keycloak port-forward svc/keycloak-operator 8084:80 &`

Or start them all with `bin/start-proxies.sh`

Expand Down
4 changes: 0 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,6 @@
"tasks:copy-certs-dev": "ts-node-dev ./src/tasks/otomi/copy-certs.ts",
"tasks:copy-certs": "node dist/tasks/otomi/copy-certs.js",
"tasks:copy-certs-argo": "node dist/tasks/otomi/copy-certs-argo.js",
"tasks:harbor-dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 ts-node-dev ./src/tasks/harbor/harbor.ts",
"tasks:harbor": "node dist/tasks/harbor/harbor.js",
"tasks:keycloak-dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 ts-node-dev ./src/tasks/keycloak/keycloak.ts",
"tasks:keycloak": "node dist/tasks/keycloak/keycloak.js",
"tasks:keycloak-users-dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 ts-node-dev ./src/tasks/keycloak/users.ts",
"tasks:otomi-chart-dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 ts-node-dev ./src/tasks/otomi/otomi-chart.ts",
"tasks:otomi-chart": "node dist/tasks/otomi/otomi-chart.js",
Expand Down
8 changes: 4 additions & 4 deletions src/operator/harbor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ async function setupHarbor() {

const config: any = {
auth_mode: 'oidc_auth',
oidc_admin_group: 'admin',
oidc_admin_group: 'platform-admin',
oidc_client_id: 'otomi',
oidc_client_secret: env.oidcClientSecret,
oidc_endpoint: env.oidcEndpoint,
Expand Down Expand Up @@ -309,7 +309,7 @@ async function getBearerToken(): Promise<HttpBearerAuth> {
// unauthenticated, so remove and recreate secret
await k8sApi.deleteNamespacedSecret(systemSecretName, systemNamespace)
// now, the next call might throw IF:
// - authMode oidc was already turned on and an otomi admin accidentally removed the secret
// - authMode oidc was already turned on and an platform admin accidentally removed the secret
// but that is very unlikely, an unresolvable problem and needs a manual db fix
robotSecret = await createSystemRobotSecret()
}
Expand Down Expand Up @@ -366,7 +366,7 @@ async function processNamespace(namespace: string) {
const projAdminMember: ProjectMember = {
roleId: HarborRole.admin,
memberGroup: {
groupName: 'team-admin',
groupName: 'all-teams-admin',
groupType: HarborGroupType.http,
},
}
Expand All @@ -377,7 +377,7 @@ async function processNamespace(namespace: string) {
)
await doApiCall(
errors,
`Associating "project-admin" role for "team-admin" with harbor project "${projectName}"`,
`Associating "project-admin" role for "all-teams-admin" with harbor project "${projectName}"`,
() => memberApi.createProjectMember(projectId, undefined, undefined, projAdminMember),
)

Expand Down
76 changes: 76 additions & 0 deletions src/operator/keycloak.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { addUserGroups, removeUserGroups } from './keycloak'

describe('Keycloak User Group Management', () => {
let api: any
let existingUser: any
let keycloakRealm: string

beforeEach(() => {
keycloakRealm = 'otomi'
existingUser = { id: 'user-id' }
api = {
users: {
realmUsersIdGroupsGet: sinon.stub(),
realmUsersIdGroupsGroupIdDelete: sinon.stub(),
realmUsersIdGroupsGroupIdPut: sinon.stub(),
},
groups: {
realmGroupsGet: sinon.stub(),
},
}
})

afterEach(() => {
sinon.restore()
})

describe('removeUserGroups', () => {
it('should remove user from groups not in teamGroups', async () => {
const existingUserGroups = [
{ name: 'group1', id: 'group1-id' },
{ name: 'group2', id: 'group2-id' },
]
api.users.realmUsersIdGroupsGet.resolves({ body: existingUserGroups })

await removeUserGroups(api, existingUser, ['group1'])

expect(api.users.realmUsersIdGroupsGroupIdDelete.calledWith(keycloakRealm, 'user-id', 'group2-id')).to.be.true
expect(api.users.realmUsersIdGroupsGroupIdDelete.calledWith(keycloakRealm, 'user-id', 'group1-id')).to.be.false
})

it('should handle errors gracefully', async () => {
api.users.realmUsersIdGroupsGet.rejects(new Error('API Error'))

await removeUserGroups(api, existingUser, ['group1'])

expect(api.users.realmUsersIdGroupsGroupIdDelete.called).to.be.false
})
})

describe('addUserGroups', () => {
it('should add user to groups in teamGroups if not already present', async () => {
const currentKeycloakGroups = [
{ name: 'group1', id: 'group1-id' },
{ name: 'group2', id: 'group2-id' },
]
const existingUserGroups = [{ name: 'group1', id: 'group1-id' }]
api.groups.realmGroupsGet.resolves({ body: currentKeycloakGroups })
api.users.realmUsersIdGroupsGet.resolves({ body: existingUserGroups })

await addUserGroups(api, existingUser, ['group1', 'group2'])

expect(api.users.realmUsersIdGroupsGroupIdPut.calledWith(keycloakRealm, 'user-id', 'group2-id')).to.be.true
expect(api.users.realmUsersIdGroupsGroupIdPut.calledWith(keycloakRealm, 'user-id', 'group1-id')).to.be.false
})

it('should handle errors gracefully', async () => {
api.groups.realmGroupsGet.rejects(new Error('API Error'))

await addUserGroups(api, existingUser, ['group1'])

expect(api.users.realmUsersIdGroupsGroupIdPut.called).to.be.false
})
})
})
Loading

0 comments on commit d086529

Please sign in to comment.