Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ Valid environment variables for the .env file. See [.env.example](/.env.example)
| `ACCESS_GROUPS_STATIC_ENABLED` | string | Yes | Flag to enable/disable automatic assignment of predefined access groups to all users. | true |
| `ACCESS_GROUPS_STATIC_VALUES` | string | Yes | Comma-separated list of access groups automatically assigned to all users. Example: "scicat, user". | |
| `ACCESS_GROUPS_OIDCPAYLOAD_ENABLED` | string | Yes | Flag to enable/disable fetching access groups directly from OIDC response. Requires specifying a field via `OIDC_ACCESS_GROUPS_PROPERTY` to extract access groups. | false |
| `ACCESS_GROUPS_LDAPPAYLOAD_ENABLED` | string | Yes | Flag to enable/disable fetching access groups directly from Ldap response. Requires specifying a field via `LDAP_ACCESS_GROUPS_PROPERTY` to extract access groups. | false |
| `DOI_PREFIX` | string | | The facility DOI prefix, with trailing slash. | |
| `DOI_SHORT_SUFFIX` | string | | By default `uuidv4` is used to generate the DOI suffix but if this flag is `true` the shorter version of 10 random characters is used as DOI suffix. | |
| `DOI_USERNAME` | string | | The facility DOI DataCite username. | |
Expand All @@ -185,6 +186,9 @@ Valid environment variables for the .env file. See [.env.example](/.env.example)
| `LDAP_BIND_CREDENTIALS` | string | Yes | Credentials for your LDAP server. | |
| `LDAP_SEARCH_BASE` | string | Yes | Search base for your LDAP server. | |
| `LDAP_SEARCH_FILTER` | string | Yes | Search filter for your LDAP server. | |
| `LDAP_GROUP_SEARCH_BASE` | string | Yes | Search base for the user groups. | |
| `LDAP_GROUP_SEARCH_FILTER` | string | Yes | Search filter for the user groups. | |
| `LDAP_ACCESS_GROUPS_PROPERTY`| string | Yes | Target field to get the access groups value from Ldap response. | |
| `OIDC_ISSUER` | string | Yes | URL of the OIDC server providing the authentication service. Example: https://identity.esss.dk/realm/ess. | |
| `OIDC_CLIENT_ID` | string | Yes | Identity of the client used to obtain the user token. Example: scicat. | |
| `OIDC_CLIENT_SECRET` | string | Yes | Secret to provide to the OIDC service to obtain the user token. Example: Aa1JIw3kv3mQlGFWhRrE3gOdkH6xreAwro. | |
Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ Valid environment variables for the .env file. See [.env.example](/.env.example)
| `LDAP_BIND_CREDENTIALS` | string | Yes | Credentials for your LDAP server. | |
| `LDAP_SEARCH_BASE` | string | Yes | Search base for your LDAP server. | |
| `LDAP_SEARCH_FILTER` | string | Yes | Search filter for your LDAP server. | |
| `LDAP_GROUP_SEARCH_BASE` | string | Yes | Search base for the user groups. | |
| `LDAP_GROUP_SEARCH_FILTER` | string | Yes | Search filter for the user groups. | |
| `OIDC_ISSUER` | string | Yes | URL of the OIDC server providing the authentication service. Example: https://identity.esss.dk/realm/ess. | |
| `OIDC_CLIENT_ID` | string | Yes | Identity of the client used to obtain the user token. Example: scicat. | |
| `OIDC_CLIENT_SECRET` | string | Yes | Secret to provide to the OIDC service to obtain the user token. Example: Aa1JIw3kv3mQlGFWhRrE3gOdkH6xreAwro. | |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ConfigService } from "@nestjs/config";
import { Test, TestingModule } from "@nestjs/testing";
import { UserPayload } from "../interfaces/userPayload.interface";
import { AccessGroupFromPayloadService } from "./access-group-from-payload.service";

describe("AccessGroupFromPayloadService", () => {
let service: AccessGroupFromPayloadService;

const mockConfigService = {
get: () => "access_group_property",
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AccessGroupFromPayloadService, ConfigService],
})
.overrideProvider(ConfigService)
.useValue(mockConfigService)
.compile();

service = module.get<AccessGroupFromPayloadService>(
AccessGroupFromPayloadService,
);
});

it("should be defined", () => {
expect(service).toBeDefined();
});

it("Should resolve access groups", async () => {
const userPayload = {
userId: "test_user",
accessGroupProperty: "_groups",
payload: {
_groups: [
{
dn: 'cn=test_group,cn=groups,cn=accounts,dc=example,dc=com',
cn: 'testgroup',
},
{
dn: 'cn=example_group,cn=groups,cn=accounts,dc=example,dc=com',
cn: 'examplegroup',
}
],
},
};
const expected = ["testgroup", "examplegroup"];
const actual = await service.getAccessGroups(userPayload as UserPayload);
expect(actual).toEqual(expected);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { UserPayload } from "../interfaces/userPayload.interface";
import { AccessGroupService } from "./access-group.service";

/**
* This service is used to get the access groups from the payload of the ldap IDP.
*/
@Injectable()
export class AccessGroupFromLdapService extends AccessGroupService {
constructor(private configService: ConfigService) {
super();
}

async getAccessGroups(userPayload: UserPayload): Promise<string[]> {
let accessGroups: string[] = [];

const accessGroupsProperty = userPayload.accessGroupProperty;
if (accessGroupsProperty) {
const payload: Record<string, unknown> | undefined = userPayload.payload;
if (
payload !== undefined &&
Array.isArray(payload[accessGroupsProperty])
) {
for (const group of payload[accessGroupsProperty]) {
if (
typeof group === "object" &&
"cn" in group &&
typeof group["cn"] === "string"
) {
accessGroups.push(group["cn"]);
}
}
}
Logger.log(accessGroups, "AccessGroupFromLdapService");
}
return accessGroups;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ export class AccessGroupFromPayloadService extends AccessGroupService {
payload !== undefined &&
Array.isArray(payload[accessGroupsProperty])
) {
accessGroups =
payload[accessGroupsProperty] !== undefined
? (payload[accessGroupsProperty] as string[])
: [];
for (var group of payload[accessGroupsProperty]) {
if (typeof group === "string") {
accessGroups.push(group);
}
}
}
Logger.log(accessGroups, "AccessGroupFromPayloadService");
}
Expand Down
13 changes: 13 additions & 0 deletions src/auth/access-group-provider/access-group-service-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AccessGroupFromStaticValuesService } from "./access-group-from-static-v
import { AccessGroupService } from "./access-group.service";
import { AccessGroupFromGraphQLApiService } from "./access-group-from-graphql-api-call.service";
import { AccessGroupFromPayloadService } from "./access-group-from-payload.service";
import { AccessGroupFromLdapService } from "./access-group-from-ldap.service";
import { HttpService } from "@nestjs/axios";
import { AccessGroupFromMultipleProvidersService } from "./access-group-from-multiple-providers.service";
import { Logger } from "@nestjs/common";
Expand All @@ -22,6 +23,9 @@ export const accessGroupServiceFactory = {
const accessGroupsOIDCPayloadConfig = configService.get(
"accessGroupsOIDCPayloadConfig",
);
const accessGroupsLdapPayloadConfig = configService.get(
"accessGroupsLdapPayloadConfig",
);

const accessGroupServices: AccessGroupService[] = [];
if (accessGroupsStaticConfig?.enabled == true) {
Expand All @@ -42,6 +46,15 @@ export const accessGroupServiceFactory = {
new AccessGroupFromPayloadService(configService),
);
}
if (accessGroupsLdapPayloadConfig?.enabled == true) {
Logger.log(
JSON.stringify(accessGroupsLdapPayloadConfig),
"loading ldap processor",
);
accessGroupServices.push(
new AccessGroupFromLdapService(configService),
);
}

if (accessGroupsGraphQlConfig?.enabled == true) {
Logger.log(
Expand Down
4 changes: 4 additions & 0 deletions src/auth/strategies/ldap.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export class LdapStrategy extends PassportStrategy(Strategy, "ldap") {
userId: user.id as string,
username: user.username,
email: user.email,
accessGroupProperty: "_groups",
payload: payload,
};
const accessGroups =
await this.accessGroupService.getAccessGroups(userPayload);
Expand Down Expand Up @@ -99,6 +101,8 @@ export class LdapStrategy extends PassportStrategy(Strategy, "ldap") {
userId: user.id as string,
username: user.username,
email: user.email,
accessGroupProperty: "_groups",
payload: payload,
};
const userIdentity = await this.usersService.findByIdUserIdentity(
user._id,
Expand Down
6 changes: 6 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ const configuration = () => {
enabled: boolean(process.env?.ACCESS_GROUPS_OIDCPAYLOAD_ENABLED || false),
accessGroupProperty: process.env?.OIDC_ACCESS_GROUPS_PROPERTY, // Example: groups
},
accessGroupsLdapPayloadConfig: {
enabled: boolean(process.env?.ACCESS_GROUPS_LDAPPAYLOAD_ENABLED || false),
accessGroupProperty: process.env?.LDAP_ACCESS_GROUPS_PROPERTY, // Example: groups
},
doiPrefix: process.env.DOI_PREFIX,
expressSession: {
secret: process.env.EXPRESS_SESSION_SECRET,
Expand All @@ -312,6 +316,8 @@ const configuration = () => {
bindCredentials: process.env.LDAP_BIND_CREDENTIALS || "",
searchBase: process.env.LDAP_SEARCH_BASE || "",
searchFilter: process.env.LDAP_SEARCH_FILTER || "",
groupSearchBase: process.env.LDAP_GROUP_SEARCH_BASE || "",
groupSearchFilter: process.env.LDAP_GROUP_SEARCH_FILTER || "",
Mode: process.env.LDAP_MODE ?? "ad",
externalIdAttr: process.env.LDAP_EXTERNAL_ID ?? "sAMAccountName",
usernameAttr: process.env.LDAP_USERNAME ?? "displayName",
Expand Down
Loading