38f185dff1
Signed-off-by: Jon Koops <jonkoops@gmail.com>
700 lines
19 KiB
TypeScript
700 lines
19 KiB
TypeScript
// tslint:disable:no-unused-expression
|
|
import { faker } from "@faker-js/faker";
|
|
import { fail } from "assert";
|
|
import * as chai from "chai";
|
|
import { omit } from "lodash-es";
|
|
import { KeycloakAdminClient } from "../src/client.js";
|
|
import type ClientRepresentation from "../src/defs/clientRepresentation.js";
|
|
import type FederatedIdentityRepresentation from "../src/defs/federatedIdentityRepresentation.js";
|
|
import type GroupRepresentation from "../src/defs/groupRepresentation.js";
|
|
import { RequiredActionAlias } from "../src/defs/requiredActionProviderRepresentation.js";
|
|
import type RoleRepresentation from "../src/defs/roleRepresentation.js";
|
|
import type UserRepresentation from "../src/defs/userRepresentation.js";
|
|
import { UnmanagedAttributePolicy } from "../src/defs/userProfileMetadata.js";
|
|
import { credentials } from "./constants.js";
|
|
|
|
const expect = chai.expect;
|
|
|
|
describe("Users", () => {
|
|
let kcAdminClient: KeycloakAdminClient;
|
|
let currentClient: ClientRepresentation;
|
|
let currentUser: UserRepresentation;
|
|
let currentRole: RoleRepresentation;
|
|
let federatedIdentity: FederatedIdentityRepresentation;
|
|
|
|
before(async () => {
|
|
kcAdminClient = new KeycloakAdminClient();
|
|
await kcAdminClient.auth(credentials);
|
|
|
|
// Enable unmanaged attributes
|
|
const currentProfileConfig = await kcAdminClient.users.getProfile();
|
|
await kcAdminClient.users.updateProfile({
|
|
...currentProfileConfig,
|
|
unmanagedAttributePolicy: UnmanagedAttributePolicy.Enabled,
|
|
});
|
|
|
|
// initialize user
|
|
const username = faker.internet.userName();
|
|
const user = await kcAdminClient.users.create({
|
|
username,
|
|
email: "test@keycloak.org",
|
|
// enabled required to be true in order to send actions email
|
|
emailVerified: true,
|
|
enabled: true,
|
|
attributes: {
|
|
key: "value",
|
|
},
|
|
});
|
|
|
|
expect(user.id).to.be.ok;
|
|
currentUser = (await kcAdminClient.users.findOne({ id: user.id }))!;
|
|
|
|
// add smtp to realm
|
|
await kcAdminClient.realms.update(
|
|
{ realm: "master" },
|
|
{
|
|
smtpServer: {
|
|
auth: true,
|
|
from: "0830021730-07fb21@inbox.mailtrap.io",
|
|
host: "smtp.mailtrap.io",
|
|
user: process.env.SMTP_USER,
|
|
password: process.env.SMTP_PWD,
|
|
},
|
|
},
|
|
);
|
|
});
|
|
|
|
after(async () => {
|
|
const userId = currentUser.id;
|
|
await kcAdminClient.users.del({
|
|
id: userId!,
|
|
});
|
|
|
|
const user = await kcAdminClient.users.findOne({
|
|
id: userId!,
|
|
});
|
|
expect(user).to.be.null;
|
|
});
|
|
|
|
it("list users", async () => {
|
|
const users = await kcAdminClient.users.find();
|
|
expect(users).to.be.ok;
|
|
});
|
|
|
|
it("count users", async () => {
|
|
const numUsers = await kcAdminClient.users.count();
|
|
// admin user + created user in before hook
|
|
expect(numUsers).to.equal(2);
|
|
});
|
|
|
|
it("count users with filter", async () => {
|
|
const numUsers = await kcAdminClient.users.count({
|
|
email: "test@keycloak.org",
|
|
});
|
|
expect(numUsers).to.equal(1);
|
|
});
|
|
|
|
it.skip("gets the profile", async () => {
|
|
const profile = await kcAdminClient.users.getProfile();
|
|
expect(profile).to.be.ok;
|
|
});
|
|
|
|
it.skip("updates the profile", async () => {
|
|
const profile = await kcAdminClient.users.updateProfile({});
|
|
expect(profile).to.be.ok;
|
|
});
|
|
|
|
it("find users by custom attributes", async () => {
|
|
// Searching by attributes is only available from Keycloak > 15
|
|
const users = await kcAdminClient.users.find({ q: "key:value" });
|
|
expect(users.length).to.be.equal(1);
|
|
expect(users[0]).to.be.deep.include(currentUser);
|
|
});
|
|
|
|
it("find users by builtin attributes", async () => {
|
|
// Searching by attributes is only available from Keycloak > 15
|
|
const users = await kcAdminClient.users.find({
|
|
q: `email:${currentUser.email}`,
|
|
});
|
|
expect(users.length).to.be.equal(1);
|
|
expect(users[0]).to.be.deep.include(currentUser);
|
|
});
|
|
|
|
it("get single users", async () => {
|
|
const userId = currentUser.id;
|
|
const user = await kcAdminClient.users.findOne({
|
|
id: userId!,
|
|
});
|
|
expect(user).to.be.deep.include(currentUser);
|
|
});
|
|
|
|
it("update single users", async () => {
|
|
const userId = currentUser.id;
|
|
await kcAdminClient.users.update(
|
|
{ id: userId! },
|
|
{
|
|
firstName: "william",
|
|
lastName: "chang",
|
|
requiredActions: [RequiredActionAlias.UPDATE_PASSWORD],
|
|
emailVerified: true,
|
|
},
|
|
);
|
|
|
|
const user = await kcAdminClient.users.findOne({
|
|
id: userId!,
|
|
});
|
|
expect(user).to.deep.include({
|
|
firstName: "william",
|
|
lastName: "chang",
|
|
requiredActions: [RequiredActionAlias.UPDATE_PASSWORD],
|
|
emailVerified: true,
|
|
});
|
|
});
|
|
|
|
/**
|
|
* reset password
|
|
*/
|
|
|
|
it("should reset user password", async () => {
|
|
const userId = currentUser.id;
|
|
await kcAdminClient.users.resetPassword({
|
|
id: userId!,
|
|
credential: {
|
|
temporary: false,
|
|
type: "password",
|
|
value: "test",
|
|
},
|
|
});
|
|
});
|
|
|
|
/**
|
|
* get user credentials
|
|
*/
|
|
it("get user credentials", async () => {
|
|
const userId = currentUser.id;
|
|
const result = await kcAdminClient.users.getCredentials({
|
|
id: userId!,
|
|
});
|
|
|
|
expect(result.map((c) => c.type)).to.include("password");
|
|
});
|
|
|
|
it("get configured user storage credential types", async () => {
|
|
const userId = currentUser.id;
|
|
const result = await kcAdminClient.users.getUserStorageCredentialTypes({
|
|
id: userId!,
|
|
});
|
|
|
|
expect(result).to.be.deep.eq([]);
|
|
});
|
|
|
|
/**
|
|
* delete user credentials
|
|
*/
|
|
it("delete user credentials", async () => {
|
|
const userId = currentUser.id;
|
|
const result = await kcAdminClient.users.getCredentials({
|
|
id: userId!,
|
|
});
|
|
|
|
expect(result.map((c) => c.type)).to.include("password");
|
|
|
|
const credential = result[0];
|
|
await kcAdminClient.users.deleteCredential({
|
|
id: userId!,
|
|
credentialId: credential.id!,
|
|
});
|
|
|
|
const credentialsAfterDelete = await kcAdminClient.users.getCredentials({
|
|
id: userId!,
|
|
});
|
|
|
|
expect(credentialsAfterDelete).to.not.deep.include(credential);
|
|
|
|
// Add deleted password back
|
|
await kcAdminClient.users.resetPassword({
|
|
id: userId!,
|
|
credential: {
|
|
temporary: false,
|
|
type: "password",
|
|
value: "test",
|
|
},
|
|
});
|
|
});
|
|
|
|
/**
|
|
* update a credential label for a user
|
|
*/
|
|
it("update a credential label for a user", async () => {
|
|
const userId = currentUser.id;
|
|
const result = await kcAdminClient.users.getCredentials({
|
|
id: userId!,
|
|
});
|
|
|
|
expect(result.map((c) => c.type)).to.include("password");
|
|
|
|
const credential = result[0];
|
|
|
|
await kcAdminClient.users.updateCredentialLabel(
|
|
{ id: userId!, credentialId: credential.id! },
|
|
"New user label",
|
|
);
|
|
|
|
const credentialsAfterLabelUpdate =
|
|
await kcAdminClient.users.getCredentials({
|
|
id: userId!,
|
|
});
|
|
|
|
expect(credentialsAfterLabelUpdate.map((c) => c.userLabel)).to.include(
|
|
"New user label",
|
|
);
|
|
});
|
|
|
|
/**
|
|
* Groups
|
|
*/
|
|
describe("user groups", () => {
|
|
let currentGroup: GroupRepresentation;
|
|
before(async () => {
|
|
const group = await kcAdminClient.groups.create({
|
|
name: "cool-group",
|
|
});
|
|
expect(group.id).to.be.ok;
|
|
currentGroup = (await kcAdminClient.groups.findOne({ id: group.id }))!;
|
|
});
|
|
|
|
after(async () => {
|
|
const groupId = currentGroup.id;
|
|
const groups = await kcAdminClient.groups.find({ max: 100 });
|
|
await Promise.all(
|
|
groups.map((_group: GroupRepresentation) => {
|
|
return kcAdminClient.groups.del({ id: _group.id! });
|
|
}),
|
|
);
|
|
|
|
const group = await kcAdminClient.groups.findOne({
|
|
id: groupId!,
|
|
});
|
|
expect(group).to.be.null;
|
|
});
|
|
|
|
it("add group", async () => {
|
|
let count = (
|
|
await kcAdminClient.users.countGroups({ id: currentUser.id! })
|
|
).count;
|
|
expect(count).to.eq(0);
|
|
await kcAdminClient.users.addToGroup({
|
|
groupId: currentGroup.id!,
|
|
id: currentUser.id!,
|
|
});
|
|
count = (await kcAdminClient.users.countGroups({ id: currentUser.id! }))
|
|
.count;
|
|
expect(count).to.eq(1);
|
|
});
|
|
|
|
it("count groups", async () => {
|
|
let { count } = await kcAdminClient.users.countGroups({
|
|
id: currentUser.id!,
|
|
});
|
|
expect(count).to.eq(1);
|
|
|
|
count = (
|
|
await kcAdminClient.users.countGroups({
|
|
id: currentUser.id!,
|
|
search: "cool-group",
|
|
})
|
|
).count;
|
|
expect(count).to.eq(1);
|
|
|
|
count = (
|
|
await kcAdminClient.users.countGroups({
|
|
id: currentUser.id!,
|
|
search: "fake-group",
|
|
})
|
|
).count;
|
|
expect(count).to.eq(0);
|
|
});
|
|
|
|
it("list groups", async () => {
|
|
const groups = await kcAdminClient.users.listGroups({
|
|
id: currentUser.id!,
|
|
});
|
|
expect(groups).to.be.ok;
|
|
expect(groups.length).to.be.eq(1);
|
|
expect(groups[0].name).to.eq("cool-group");
|
|
});
|
|
|
|
it("remove group", async () => {
|
|
const newGroup = await kcAdminClient.groups.create({
|
|
name: "test-group",
|
|
});
|
|
await kcAdminClient.users.addToGroup({
|
|
id: currentUser.id!,
|
|
groupId: newGroup.id,
|
|
});
|
|
let count = (
|
|
await kcAdminClient.users.countGroups({ id: currentUser.id! })
|
|
).count;
|
|
expect(count).to.eq(2);
|
|
|
|
try {
|
|
await kcAdminClient.users.delFromGroup({
|
|
id: currentUser.id!,
|
|
groupId: newGroup.id,
|
|
});
|
|
} catch {
|
|
fail("Didn't expect an error when deleting a valid group id");
|
|
}
|
|
|
|
count = (await kcAdminClient.users.countGroups({ id: currentUser.id! }))
|
|
.count;
|
|
expect(count).to.equal(1);
|
|
|
|
await kcAdminClient.groups.del({ id: newGroup.id });
|
|
|
|
// delete a non-existing group should throw an error
|
|
try {
|
|
await kcAdminClient.users.delFromGroup({
|
|
id: currentUser.id!,
|
|
groupId: "fake-group-id",
|
|
});
|
|
fail(
|
|
"Expected an error when deleting a fake id not assigned to the user",
|
|
);
|
|
} catch (e) {
|
|
expect(e).to.be.ok;
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Role mappings
|
|
*/
|
|
describe("role-mappings", () => {
|
|
before(async () => {
|
|
// create new role
|
|
const roleName = faker.internet.userName();
|
|
await kcAdminClient.roles.create({
|
|
name: roleName,
|
|
});
|
|
const role = await kcAdminClient.roles.findOneByName({
|
|
name: roleName,
|
|
});
|
|
currentRole = role!;
|
|
});
|
|
|
|
after(async () => {
|
|
await kcAdminClient.roles.delByName({ name: currentRole.name! });
|
|
});
|
|
|
|
it("add a role to user", async () => {
|
|
// add role-mappings with role id
|
|
await kcAdminClient.users.addRealmRoleMappings({
|
|
id: currentUser.id!,
|
|
|
|
// at least id and name should appear
|
|
roles: [
|
|
{
|
|
id: currentRole.id!,
|
|
name: currentRole.name!,
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("list available role-mappings for user", async () => {
|
|
const roles = await kcAdminClient.users.listAvailableRealmRoleMappings({
|
|
id: currentUser.id!,
|
|
});
|
|
|
|
// admin, create-realm
|
|
// not sure why others like offline_access, uma_authorization not included
|
|
expect(roles.length).to.be.least(2);
|
|
});
|
|
|
|
it("list role-mappings of user", async () => {
|
|
const res = await kcAdminClient.users.listRoleMappings({
|
|
id: currentUser.id!,
|
|
});
|
|
|
|
expect(res).have.all.keys("realmMappings");
|
|
});
|
|
|
|
it("list realm role-mappings of user", async () => {
|
|
const roles = await kcAdminClient.users.listRealmRoleMappings({
|
|
id: currentUser.id!,
|
|
});
|
|
// currentRole will have an empty `attributes`, but role-mappings do not
|
|
expect(roles).to.deep.include(omit(currentRole, "attributes"));
|
|
});
|
|
|
|
it("list realm composite role-mappings of user", async () => {
|
|
const roles = await kcAdminClient.users.listCompositeRealmRoleMappings({
|
|
id: currentUser.id!,
|
|
});
|
|
// todo: add data integrity check later
|
|
expect(roles).to.be.ok;
|
|
});
|
|
|
|
it("del realm role-mappings from user", async () => {
|
|
await kcAdminClient.users.delRealmRoleMappings({
|
|
id: currentUser.id!,
|
|
roles: [
|
|
{
|
|
id: currentRole.id!,
|
|
name: currentRole.name!,
|
|
},
|
|
],
|
|
});
|
|
|
|
const roles = await kcAdminClient.users.listRealmRoleMappings({
|
|
id: currentUser.id!,
|
|
});
|
|
expect(roles).to.not.deep.include(currentRole);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* client Role mappings
|
|
*/
|
|
describe("client role-mappings", () => {
|
|
before(async () => {
|
|
// create new client
|
|
const clientId = faker.internet.userName();
|
|
await kcAdminClient.clients.create({
|
|
clientId,
|
|
});
|
|
|
|
const clients = await kcAdminClient.clients.find({ clientId });
|
|
expect(clients[0]).to.be.ok;
|
|
currentClient = clients[0];
|
|
|
|
// create new client role
|
|
const roleName = faker.internet.userName();
|
|
await kcAdminClient.clients.createRole({
|
|
id: currentClient.id,
|
|
name: roleName,
|
|
});
|
|
|
|
// assign to currentRole
|
|
currentRole = await kcAdminClient.clients.findRole({
|
|
id: currentClient.id!,
|
|
roleName,
|
|
});
|
|
});
|
|
|
|
after(async () => {
|
|
await kcAdminClient.clients.delRole({
|
|
id: currentClient.id!,
|
|
roleName: currentRole.name!,
|
|
});
|
|
await kcAdminClient.clients.del({ id: currentClient.id! });
|
|
});
|
|
|
|
it("add a client role to user", async () => {
|
|
// add role-mappings with role id
|
|
await kcAdminClient.users.addClientRoleMappings({
|
|
id: currentUser.id!,
|
|
clientUniqueId: currentClient.id!,
|
|
|
|
// at least id and name should appear
|
|
roles: [
|
|
{
|
|
id: currentRole.id!,
|
|
name: currentRole.name!,
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("list available client role-mappings for user", async () => {
|
|
const roles = await kcAdminClient.users.listAvailableClientRoleMappings({
|
|
id: currentUser.id!,
|
|
clientUniqueId: currentClient.id!,
|
|
});
|
|
|
|
expect(roles).to.be.empty;
|
|
});
|
|
|
|
it("list composite client role-mappings for user", async () => {
|
|
const roles = await kcAdminClient.users.listCompositeClientRoleMappings({
|
|
id: currentUser.id!,
|
|
clientUniqueId: currentClient.id!,
|
|
});
|
|
|
|
expect(roles).to.be.ok;
|
|
});
|
|
|
|
it("list client role-mappings of user", async () => {
|
|
const roles = await kcAdminClient.users.listClientRoleMappings({
|
|
id: currentUser.id!,
|
|
clientUniqueId: currentClient.id!,
|
|
});
|
|
|
|
// currentRole will have an empty `attributes`, but role-mappings do not
|
|
expect(currentRole).to.deep.include(roles[0]);
|
|
});
|
|
|
|
it("del client role-mappings from user", async () => {
|
|
const roleName = faker.internet.userName();
|
|
await kcAdminClient.clients.createRole({
|
|
id: currentClient.id,
|
|
name: roleName,
|
|
});
|
|
const role = await kcAdminClient.clients.findRole({
|
|
id: currentClient.id!,
|
|
roleName,
|
|
});
|
|
|
|
// delete the created role
|
|
await kcAdminClient.users.delClientRoleMappings({
|
|
id: currentUser.id!,
|
|
clientUniqueId: currentClient.id!,
|
|
roles: [
|
|
{
|
|
id: role.id!,
|
|
name: role.name!,
|
|
},
|
|
],
|
|
});
|
|
|
|
// check if mapping is successfully deleted
|
|
const roles = await kcAdminClient.users.listClientRoleMappings({
|
|
id: currentUser.id!,
|
|
clientUniqueId: currentClient.id!,
|
|
});
|
|
|
|
// should only left the one we added in the previous test
|
|
expect(roles.length).to.be.eql(1);
|
|
});
|
|
});
|
|
|
|
describe("User sessions", () => {
|
|
before(async () => {
|
|
kcAdminClient = new KeycloakAdminClient();
|
|
await kcAdminClient.auth(credentials);
|
|
|
|
// create client
|
|
const clientId = faker.internet.userName();
|
|
await kcAdminClient.clients.create({
|
|
clientId,
|
|
consentRequired: true,
|
|
});
|
|
|
|
const clients = await kcAdminClient.clients.find({ clientId });
|
|
expect(clients[0]).to.be.ok;
|
|
currentClient = clients[0];
|
|
});
|
|
|
|
after(async () => {
|
|
await kcAdminClient.clients.del({
|
|
id: currentClient.id!,
|
|
});
|
|
});
|
|
|
|
it("list user sessions", async () => {
|
|
// @TODO: In order to test it, currentUser has to be logged in
|
|
const userSessions = await kcAdminClient.users.listSessions({
|
|
id: currentUser.id!,
|
|
});
|
|
|
|
expect(userSessions).to.be.ok;
|
|
});
|
|
|
|
it("list users off-line sessions", async () => {
|
|
// @TODO: In order to test it, currentUser has to be logged in
|
|
const userOfflineSessions = await kcAdminClient.users.listOfflineSessions(
|
|
{ id: currentUser.id!, clientId: currentClient.id! },
|
|
);
|
|
|
|
expect(userOfflineSessions).to.be.ok;
|
|
});
|
|
|
|
it("logout user from all sessions", async () => {
|
|
// @TODO: In order to test it, currentUser has to be logged in
|
|
await kcAdminClient.users.logout({ id: currentUser.id! });
|
|
});
|
|
|
|
it("list consents granted by the user", async () => {
|
|
const consents = await kcAdminClient.users.listConsents({
|
|
id: currentUser.id!,
|
|
});
|
|
|
|
expect(consents).to.be.ok;
|
|
});
|
|
|
|
it("revoke consent and offline tokens for particular client", async () => {
|
|
// @TODO: In order to test it, currentUser has to granted consent to client
|
|
const consents = await kcAdminClient.users.listConsents({
|
|
id: currentUser.id!,
|
|
});
|
|
|
|
if (consents.length) {
|
|
const consent = consents[0];
|
|
|
|
await kcAdminClient.users.revokeConsent({
|
|
id: currentUser.id!,
|
|
clientId: consent.clientId!,
|
|
});
|
|
}
|
|
});
|
|
|
|
it("impersonate user", async () => {
|
|
const result = await kcAdminClient.users.impersonation(
|
|
{ id: currentUser.id! },
|
|
{ user: currentUser.id!, realm: kcAdminClient.realmName },
|
|
);
|
|
expect(result).to.be.ok;
|
|
await kcAdminClient.auth(credentials);
|
|
});
|
|
});
|
|
|
|
describe("Federated Identity user integration", () => {
|
|
before(async () => {
|
|
kcAdminClient = new KeycloakAdminClient();
|
|
await kcAdminClient.auth(credentials);
|
|
|
|
federatedIdentity = {
|
|
identityProvider: "foobar",
|
|
userId: "userid1",
|
|
userName: "username1",
|
|
};
|
|
});
|
|
|
|
it("should list user federated identities and expect empty", async () => {
|
|
const federatedIdentities =
|
|
await kcAdminClient.users.listFederatedIdentities({
|
|
id: currentUser.id!,
|
|
});
|
|
expect(federatedIdentities).to.be.eql([]);
|
|
});
|
|
|
|
it("should add federated identity to user", async () => {
|
|
await kcAdminClient.users.addToFederatedIdentity({
|
|
id: currentUser.id!,
|
|
federatedIdentityId: "foobar",
|
|
federatedIdentity,
|
|
});
|
|
|
|
// @TODO: In order to test the integration with federated identities, the User Federation
|
|
// would need to be created first, this is not implemented yet.
|
|
// const federatedIdentities = await kcAdminClient.users.listFederatedIdentities({
|
|
// id: currentUser.id,
|
|
// });
|
|
// expect(federatedIdentities[0]).to.be.eql(federatedIdentity);
|
|
});
|
|
|
|
it("should remove federated identity from user", async () => {
|
|
await kcAdminClient.users.delFromFederatedIdentity({
|
|
id: currentUser.id!,
|
|
federatedIdentityId: "foobar",
|
|
});
|
|
|
|
const federatedIdentities =
|
|
await kcAdminClient.users.listFederatedIdentities({
|
|
id: currentUser.id!,
|
|
});
|
|
expect(federatedIdentities).to.be.eql([]);
|
|
});
|
|
});
|
|
});
|