[BREAK] Moved role-sync and advanced SAML settings to EE (#23107)

* migrated sync role to enterprise

* migrated advanced SAML features to ee

* Reorganized SAML settings

Co-authored-by: Pierre Lehnen <pierre.lehnen@rocket.chat>
pull/23263/head
Leonardo Ostjen Couto 4 years ago committed by GitHub
parent 68e6270111
commit 15b5b1ac98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/meteor-accounts-saml/server/definition/ISAMLGlobalSettings.ts
  2. 2
      app/meteor-accounts-saml/server/definition/IServiceProviderOptions.ts
  3. 16
      app/meteor-accounts-saml/server/lib/SAML.ts
  4. 45
      app/meteor-accounts-saml/server/lib/Utils.ts
  5. 2
      app/meteor-accounts-saml/server/lib/parsers/Response.ts
  6. 454
      app/meteor-accounts-saml/server/lib/settings.ts
  7. 10
      app/meteor-accounts-saml/server/startup.ts
  8. 2
      app/meteor-accounts-saml/tests/data.ts
  9. 35
      app/meteor-accounts-saml/tests/server.tests.ts
  10. 1
      ee/app/license/server/bundles.ts
  11. 2
      ee/server/configuration/index.ts
  12. 44
      ee/server/configuration/saml.ts
  13. 3
      ee/server/index.js
  14. 1
      ee/server/settings/index.ts
  15. 113
      ee/server/settings/saml.ts
  16. 3
      packages/rocketchat-i18n/i18n/en.i18n.json

@ -4,8 +4,6 @@ export interface ISAMLGlobalSettings {
mailOverwrite: boolean;
immutableProperty: string;
defaultUserRole: string;
roleAttributeName: string;
roleAttributeSync: boolean;
userDataFieldMap: string;
usernameNormalize: string;
channelsAttributeUpdate: boolean;

@ -9,8 +9,6 @@ export interface IServiceProviderOptions {
customAuthnContext: string;
authnContextComparison: string;
defaultUserRole: string;
roleAttributeName: string;
roleAttributeSync: boolean;
allowedClockDrift: number;
signatureValidationType: string;
identifierFormat: string;

@ -72,7 +72,7 @@ export class SAML {
}
public static insertOrUpdateSAMLUser(userObject: ISAMLUser): {userId: string; token: string} {
const { roleAttributeSync, generateUsername, immutableProperty, nameOverwrite, mailOverwrite, channelsAttributeUpdate } = SAMLUtils.globalSettings;
const { generateUsername, immutableProperty, nameOverwrite, mailOverwrite, channelsAttributeUpdate } = SAMLUtils.globalSettings;
let customIdentifierMatch = false;
let customIdentifierAttributeName: string | null = null;
@ -103,8 +103,8 @@ export class SAML {
address: email,
verified: settings.get('Accounts_Verify_Email_For_External_Accounts'),
}));
const globalRoles = userObject.roles;
const { roles } = userObject;
let { username } = userObject;
const active = !settings.get('Accounts_ManuallyApproveNewUsers');
@ -113,7 +113,7 @@ export class SAML {
const newUser: Record<string, any> = {
name: userObject.fullName,
active,
globalRoles,
globalRoles: roles,
emails,
services: {
saml: {
@ -184,8 +184,8 @@ export class SAML {
updateData.name = userObject.fullName;
}
if (roleAttributeSync) {
updateData.roles = globalRoles;
if (roles) {
updateData.roles = roles;
}
if (userObject.channels && channelsAttributeUpdate === true) {
@ -216,7 +216,7 @@ export class SAML {
res.writeHead(200);
res.write(serviceProvider.generateServiceProviderMetadata());
res.end();
} catch (err) {
} catch (err: any) {
showErrorMessage(res, err);
}
}
@ -300,7 +300,7 @@ export class SAML {
redirect(url);
});
} catch (e) {
} catch (e: any) {
SystemLogger.error(e);
redirect();
}
@ -472,7 +472,7 @@ export class SAML {
}
}
}
} catch (err) {
} catch (err: any) {
SystemLogger.error(err);
}
}

@ -1,4 +1,5 @@
import zlib from 'zlib';
import { EventEmitter } from 'events';
import _ from 'underscore';
@ -7,15 +8,12 @@ import { ISAMLUser } from '../definition/ISAMLUser';
import { ISAMLGlobalSettings } from '../definition/ISAMLGlobalSettings';
import { IUserDataMap, IAttributeMapping } from '../definition/IAttributeMapping';
import { StatusCode } from './constants';
// @ToDo remove this ts-ignore someday
// @ts-ignore skip checking if Logger exists to avoid having to import the Logger class here (it would bring a lot of baggage with its dependencies, affecting the unit tests)
type NullableLogger = Logger | null;
import { Logger } from '../../../../server/lib/logger/Logger';
let providerList: Array<IServiceProviderOptions> = [];
let debug = false;
let relayState: string | null = null;
let logger: NullableLogger = null;
let logger: Logger | undefined;
const globalSettings: ISAMLGlobalSettings = {
generateUsername: false,
@ -23,8 +21,6 @@ const globalSettings: ISAMLGlobalSettings = {
mailOverwrite: false,
immutableProperty: 'EMail',
defaultUserRole: 'user',
roleAttributeName: '',
roleAttributeSync: false,
userDataFieldMap: '{"username":"username", "email":"email", "cn": "name"}',
usernameNormalize: 'None',
channelsAttributeUpdate: false,
@ -32,6 +28,8 @@ const globalSettings: ISAMLGlobalSettings = {
};
export class SAMLUtils {
public static events: EventEmitter;
public static get isDebugging(): boolean {
return debug;
}
@ -53,8 +51,7 @@ export class SAMLUtils {
}
public static getServiceProviderOptions(providerName: string): IServiceProviderOptions | undefined {
this.log(providerName);
this.log(providerList);
this.log(providerName, providerList);
return _.find(providerList, (providerOptions) => providerOptions.provider === providerName);
}
@ -63,7 +60,7 @@ export class SAMLUtils {
providerList = list;
}
public static setLoggerInstance(instance: NullableLogger): void {
public static setLoggerInstance(instance: Logger): void {
logger = instance;
}
@ -74,7 +71,6 @@ export class SAMLUtils {
globalSettings.generateUsername = Boolean(samlConfigs.generateUsername);
globalSettings.nameOverwrite = Boolean(samlConfigs.nameOverwrite);
globalSettings.mailOverwrite = Boolean(samlConfigs.mailOverwrite);
globalSettings.roleAttributeSync = Boolean(samlConfigs.roleAttributeSync);
globalSettings.channelsAttributeUpdate = Boolean(samlConfigs.channelsAttributeUpdate);
globalSettings.includePrivateChannelsInUpdate = Boolean(samlConfigs.includePrivateChannelsInUpdate);
@ -90,10 +86,6 @@ export class SAMLUtils {
globalSettings.defaultUserRole = samlConfigs.defaultUserRole;
}
if (samlConfigs.roleAttributeName && typeof samlConfigs.roleAttributeName === 'string') {
globalSettings.roleAttributeName = samlConfigs.roleAttributeName;
}
if (samlConfigs.userDataFieldMap && typeof samlConfigs.userDataFieldMap === 'string') {
globalSettings.userDataFieldMap = samlConfigs.userDataFieldMap;
}
@ -139,15 +131,15 @@ export class SAMLUtils {
return newTemplate;
}
public static log(...args: Array<any>): void {
public static log(obj: any, ...args: Array<any>): void {
if (debug && logger) {
logger.debug(...args);
logger.debug(obj, ...args);
}
}
public static error(...args: Array<any>): void {
public static error(obj: any, ...args: Array<any>): void {
if (logger) {
logger.error(...args);
logger.error(obj, ...args);
}
}
@ -421,7 +413,7 @@ export class SAMLUtils {
public static mapProfileToUserObject(profile: Record<string, any>): ISAMLUser {
const userDataMap = this.getUserDataMapping();
SAMLUtils.log('parsed userDataMap', userDataMap);
const { defaultUserRole = 'user', roleAttributeName } = this.globalSettings;
const { defaultUserRole = 'user' } = this.globalSettings;
if (userDataMap.identifier.type === 'custom') {
if (!userDataMap.identifier.attribute) {
@ -470,15 +462,6 @@ export class SAMLUtils {
userObject.username = this.normalizeUsername(profileUsername);
}
if (roleAttributeName && profile[roleAttributeName]) {
let value = profile[roleAttributeName] || '';
if (typeof value === 'string') {
value = value.split(',');
}
userObject.roles = this.ensureArray<string>(value);
}
if (profile.language) {
userObject.language = profile.language;
}
@ -498,6 +481,10 @@ export class SAMLUtils {
}
}
this.events.emit('mapUser', { profile, userObject });
return userObject;
}
}
SAMLUtils.events = new EventEmitter();

@ -342,7 +342,7 @@ export class ResponseParser {
private validateNotBeforeNotOnOrAfterAssertions(element: Element): boolean {
const sysnow = new Date();
const allowedclockdrift = this.serviceProviderOptions.allowedClockDrift;
const allowedclockdrift = this.serviceProviderOptions.allowedClockDrift || 0;
const now = new Date(sysnow.getTime() + allowedclockdrift);

@ -19,7 +19,7 @@ import {
} from './constants';
export const getSamlConfigs = function(service: string): Record<string, any> {
return {
const configs = {
buttonLabelText: settings.get(`${ service }_button_label_text`),
buttonLabelColor: settings.get(`${ service }_button_label_color`),
buttonColor: settings.get(`${ service }_button_color`),
@ -36,11 +36,7 @@ export const getSamlConfigs = function(service: string): Record<string, any> {
mailOverwrite: settings.get(`${ service }_mail_overwrite`),
issuer: settings.get(`${ service }_issuer`),
logoutBehaviour: settings.get(`${ service }_logout_behaviour`),
customAuthnContext: settings.get(`${ service }_custom_authn_context`),
authnContextComparison: settings.get(`${ service }_authn_context_comparison`),
defaultUserRole: settings.get(`${ service }_default_user_role`),
roleAttributeName: settings.get(`${ service }_role_attribute_name`),
roleAttributeSync: settings.get(`${ service }_role_attribute_sync`),
secret: {
privateKey: settings.get(`${ service }_private_key`),
publicCert: settings.get(`${ service }_public_cert`),
@ -50,17 +46,22 @@ export const getSamlConfigs = function(service: string): Record<string, any> {
signatureValidationType: settings.get(`${ service }_signature_validation_type`),
userDataFieldMap: settings.get(`${ service }_user_data_fieldmap`),
allowedClockDrift: settings.get(`${ service }_allowed_clock_drift`),
identifierFormat: settings.get(`${ service }_identifier_format`),
nameIDPolicyTemplate: settings.get(`${ service }_NameId_template`),
authnContextTemplate: settings.get(`${ service }_AuthnContext_template`),
authRequestTemplate: settings.get(`${ service }_AuthRequest_template`),
logoutResponseTemplate: settings.get(`${ service }_LogoutResponse_template`),
logoutRequestTemplate: settings.get(`${ service }_LogoutRequest_template`),
metadataCertificateTemplate: settings.get(`${ service }_MetadataCertificate_template`),
metadataTemplate: settings.get(`${ service }_Metadata_template`),
customAuthnContext: defaultAuthnContext,
authnContextComparison: 'exact',
identifierFormat: defaultIdentifierFormat,
nameIDPolicyTemplate: defaultNameIDTemplate,
authnContextTemplate: defaultAuthnContextTemplate,
authRequestTemplate: defaultAuthRequestTemplate,
logoutResponseTemplate: defaultLogoutResponseTemplate,
logoutRequestTemplate: defaultLogoutRequestTemplate,
metadataCertificateTemplate: defaultMetadataCertificateTemplate,
metadataTemplate: defaultMetadataTemplate,
channelsAttributeUpdate: settings.get(`${ service }_channels_update`),
includePrivateChannelsInUpdate: settings.get(`${ service }_include_private_channels_update`),
};
SAMLUtils.events.emit('loadConfigs', service, configs);
return configs;
};
export const configureSamlService = function(samlConfigs: Record<string, any>): IServiceProviderOptions {
@ -87,8 +88,6 @@ export const configureSamlService = function(samlConfigs: Record<string, any>):
customAuthnContext: samlConfigs.customAuthnContext,
authnContextComparison: samlConfigs.authnContextComparison,
defaultUserRole: samlConfigs.defaultUserRole,
roleAttributeName: samlConfigs.roleAttributeName,
roleAttributeSync: samlConfigs.roleAttributeSync,
allowedClockDrift: parseInt(samlConfigs.allowedClockDrift) || 0,
signatureValidationType: samlConfigs.signatureValidationType,
identifierFormat: samlConfigs.identifierFormat,
@ -136,290 +135,159 @@ export const addSamlService = function(name: string): void {
};
export const addSettings = function(name: string): void {
settings.add(`SAML_Custom_${ name }`, false, {
type: 'boolean',
group: 'SAML',
i18nLabel: 'Accounts_OAuth_Custom_Enable',
});
settings.add(`SAML_Custom_${ name }_provider`, 'provider-name', {
type: 'string',
group: 'SAML',
i18nLabel: 'SAML_Custom_Provider',
});
settings.add(`SAML_Custom_${ name }_entry_point`, 'https://example.com/simplesaml/saml2/idp/SSOService.php', {
type: 'string',
group: 'SAML',
i18nLabel: 'SAML_Custom_Entry_point',
});
settings.add(`SAML_Custom_${ name }_idp_slo_redirect_url`, 'https://example.com/simplesaml/saml2/idp/SingleLogoutService.php', {
type: 'string',
group: 'SAML',
i18nLabel: 'SAML_Custom_IDP_SLO_Redirect_URL',
});
settings.add(`SAML_Custom_${ name }_issuer`, 'https://your-rocket-chat/_saml/metadata/provider-name', {
type: 'string',
group: 'SAML',
i18nLabel: 'SAML_Custom_Issuer',
});
settings.add(`SAML_Custom_${ name }_debug`, false, {
type: 'boolean',
group: 'SAML',
i18nLabel: 'SAML_Custom_Debug',
});
// UI Settings
settings.add(`SAML_Custom_${ name }_button_label_text`, 'SAML', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_1_User_Interface',
i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text',
});
settings.add(`SAML_Custom_${ name }_button_label_color`, '#FFFFFF', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_1_User_Interface',
i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color',
});
settings.add(`SAML_Custom_${ name }_button_color`, '#1d74f5', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_1_User_Interface',
i18nLabel: 'Accounts_OAuth_Custom_Button_Color',
});
// Certificate settings
settings.add(`SAML_Custom_${ name }_cert`, '', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_2_Certificate',
i18nLabel: 'SAML_Custom_Cert',
multiline: true,
secret: true,
});
settings.add(`SAML_Custom_${ name }_public_cert`, '', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_2_Certificate',
multiline: true,
i18nLabel: 'SAML_Custom_Public_Cert',
});
settings.add(`SAML_Custom_${ name }_signature_validation_type`, 'All', {
type: 'select',
values: [
{ key: 'Response', i18nLabel: 'SAML_Custom_signature_validation_response' },
{ key: 'Assertion', i18nLabel: 'SAML_Custom_signature_validation_assertion' },
{ key: 'Either', i18nLabel: 'SAML_Custom_signature_validation_either' },
{ key: 'All', i18nLabel: 'SAML_Custom_signature_validation_all' },
],
group: 'SAML',
section: 'SAML_Section_2_Certificate',
i18nLabel: 'SAML_Custom_signature_validation_type',
i18nDescription: 'SAML_Custom_signature_validation_type_description',
});
settings.add(`SAML_Custom_${ name }_private_key`, '', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_2_Certificate',
multiline: true,
i18nLabel: 'SAML_Custom_Private_Key',
secret: true,
});
// Settings to customize behavior
settings.add(`SAML_Custom_${ name }_generate_username`, false, {
type: 'boolean',
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_Generate_Username',
});
settings.add(`SAML_Custom_${ name }_username_normalize`, 'None', {
type: 'select',
values: [
{ key: 'None', i18nLabel: 'SAML_Custom_Username_Normalize_None' },
{ key: 'Lowercase', i18nLabel: 'SAML_Custom_Username_Normalize_Lowercase' },
],
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_Username_Normalize',
});
settings.add(`SAML_Custom_${ name }_immutable_property`, 'EMail', {
type: 'select',
values: [
{ key: 'Username', i18nLabel: 'SAML_Custom_Immutable_Property_Username' },
{ key: 'EMail', i18nLabel: 'SAML_Custom_Immutable_Property_EMail' },
],
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_Immutable_Property',
});
settings.add(`SAML_Custom_${ name }_name_overwrite`, false, {
type: 'boolean',
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_name_overwrite',
});
settings.add(`SAML_Custom_${ name }_mail_overwrite`, false, {
type: 'boolean',
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_mail_overwrite',
});
settings.add(`SAML_Custom_${ name }_logout_behaviour`, 'SAML', {
type: 'select',
values: [
{ key: 'SAML', i18nLabel: 'SAML_Custom_Logout_Behaviour_Terminate_SAML_Session' },
{ key: 'Local', i18nLabel: 'SAML_Custom_Logout_Behaviour_End_Only_RocketChat' },
],
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_Logout_Behaviour',
});
settings.add(`SAML_Custom_${ name }_channels_update`, false, {
type: 'boolean',
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_channels_update',
i18nDescription: 'SAML_Custom_channels_update_description',
});
settings.add(`SAML_Custom_${ name }_include_private_channels_update`, false, {
type: 'boolean',
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_include_private_channels_update',
i18nDescription: 'SAML_Custom_include_private_channels_update_description',
});
// Roles Settings
settings.add(`SAML_Custom_${ name }_default_user_role`, 'user', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_4_Roles',
i18nLabel: 'SAML_Default_User_Role',
i18nDescription: 'SAML_Default_User_Role_Description',
});
settings.add(`SAML_Custom_${ name }_role_attribute_name`, '', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_4_Roles',
i18nLabel: 'SAML_Role_Attribute_Name',
i18nDescription: 'SAML_Role_Attribute_Name_Description',
});
settings.add(`SAML_Custom_${ name }_role_attribute_sync`, false, {
type: 'boolean',
group: 'SAML',
section: 'SAML_Section_4_Roles',
i18nLabel: 'SAML_Role_Attribute_Sync',
i18nDescription: 'SAML_Role_Attribute_Sync_Description',
});
// Data Mapping Settings
settings.add(`SAML_Custom_${ name }_user_data_fieldmap`, '{"username":"username", "email":"email", "name": "cn"}', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_5_Mapping',
i18nLabel: 'SAML_Custom_user_data_fieldmap',
i18nDescription: 'SAML_Custom_user_data_fieldmap_description',
multiline: true,
});
// Advanced settings
settings.add(`SAML_Custom_${ name }_allowed_clock_drift`, 0, {
type: 'int',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_Allowed_Clock_Drift',
i18nDescription: 'SAML_Allowed_Clock_Drift_Description',
});
settings.add(`SAML_Custom_${ name }_identifier_format`, defaultIdentifierFormat, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_Identifier_Format',
i18nDescription: 'SAML_Identifier_Format_Description',
});
settings.add(`SAML_Custom_${ name }_NameId_template`, defaultNameIDTemplate, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_NameIdPolicy_Template',
i18nDescription: 'SAML_NameIdPolicy_Template_Description',
multiline: true,
});
settings.add(`SAML_Custom_${ name }_custom_authn_context`, defaultAuthnContext, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_Custom_Authn_Context',
i18nDescription: 'SAML_Custom_Authn_Context_description',
});
settings.add(`SAML_Custom_${ name }_authn_context_comparison`, 'exact', {
type: 'select',
values: [
{ key: 'better', i18nLabel: 'Better' },
{ key: 'exact', i18nLabel: 'Exact' },
{ key: 'maximum', i18nLabel: 'Maximum' },
{ key: 'minimum', i18nLabel: 'Minimum' },
],
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_Custom_Authn_Context_Comparison',
});
settings.addGroup('SAML', function() {
this.set({
tab: 'SAML_Connection',
}, function() {
this.add(`SAML_Custom_${ name }`, false, {
type: 'boolean',
i18nLabel: 'Accounts_OAuth_Custom_Enable',
});
this.add(`SAML_Custom_${ name }_provider`, 'provider-name', {
type: 'string',
i18nLabel: 'SAML_Custom_Provider',
});
this.add(`SAML_Custom_${ name }_entry_point`, 'https://example.com/simplesaml/saml2/idp/SSOService.php', {
type: 'string',
i18nLabel: 'SAML_Custom_Entry_point',
});
this.add(`SAML_Custom_${ name }_idp_slo_redirect_url`, 'https://example.com/simplesaml/saml2/idp/SingleLogoutService.php', {
type: 'string',
i18nLabel: 'SAML_Custom_IDP_SLO_Redirect_URL',
});
this.add(`SAML_Custom_${ name }_issuer`, 'https://your-rocket-chat/_saml/metadata/provider-name', {
type: 'string',
i18nLabel: 'SAML_Custom_Issuer',
});
this.add(`SAML_Custom_${ name }_debug`, false, {
type: 'boolean',
i18nLabel: 'SAML_Custom_Debug',
});
settings.add(`SAML_Custom_${ name }_AuthnContext_template`, defaultAuthnContextTemplate, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_AuthnContext_Template',
i18nDescription: 'SAML_AuthnContext_Template_Description',
multiline: true,
});
this.section('SAML_Section_2_Certificate', function() {
this.add(`SAML_Custom_${ name }_cert`, '', {
type: 'string',
i18nLabel: 'SAML_Custom_Cert',
multiline: true,
secret: true,
});
this.add(`SAML_Custom_${ name }_public_cert`, '', {
type: 'string',
multiline: true,
i18nLabel: 'SAML_Custom_Public_Cert',
});
this.add(`SAML_Custom_${ name }_signature_validation_type`, 'All', {
type: 'select',
values: [
{ key: 'Response', i18nLabel: 'SAML_Custom_signature_validation_response' },
{ key: 'Assertion', i18nLabel: 'SAML_Custom_signature_validation_assertion' },
{ key: 'Either', i18nLabel: 'SAML_Custom_signature_validation_either' },
{ key: 'All', i18nLabel: 'SAML_Custom_signature_validation_all' },
],
i18nLabel: 'SAML_Custom_signature_validation_type',
i18nDescription: 'SAML_Custom_signature_validation_type_description',
});
this.add(`SAML_Custom_${ name }_private_key`, '', {
type: 'string',
multiline: true,
i18nLabel: 'SAML_Custom_Private_Key',
secret: true,
});
});
});
this.set({
tab: 'SAML_General',
}, function() {
this.section('SAML_Section_1_User_Interface', function() {
this.add(`SAML_Custom_${ name }_button_label_text`, 'SAML', {
type: 'string',
i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text',
});
this.add(`SAML_Custom_${ name }_button_label_color`, '#FFFFFF', {
type: 'string',
i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color',
});
this.add(`SAML_Custom_${ name }_button_color`, '#1d74f5', {
type: 'string',
i18nLabel: 'Accounts_OAuth_Custom_Button_Color',
});
});
settings.add(`SAML_Custom_${ name }_AuthRequest_template`, defaultAuthRequestTemplate, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_AuthnRequest_Template',
i18nDescription: 'SAML_AuthnRequest_Template_Description',
multiline: true,
});
this.section('SAML_Section_3_Behavior', function() {
// Settings to customize behavior
this.add(`SAML_Custom_${ name }_generate_username`, false, {
type: 'boolean',
i18nLabel: 'SAML_Custom_Generate_Username',
});
this.add(`SAML_Custom_${ name }_username_normalize`, 'None', {
type: 'select',
values: [
{ key: 'None', i18nLabel: 'SAML_Custom_Username_Normalize_None' },
{ key: 'Lowercase', i18nLabel: 'SAML_Custom_Username_Normalize_Lowercase' },
],
i18nLabel: 'SAML_Custom_Username_Normalize',
});
this.add(`SAML_Custom_${ name }_immutable_property`, 'EMail', {
type: 'select',
values: [
{ key: 'Username', i18nLabel: 'SAML_Custom_Immutable_Property_Username' },
{ key: 'EMail', i18nLabel: 'SAML_Custom_Immutable_Property_EMail' },
],
i18nLabel: 'SAML_Custom_Immutable_Property',
});
this.add(`SAML_Custom_${ name }_name_overwrite`, false, {
type: 'boolean',
i18nLabel: 'SAML_Custom_name_overwrite',
});
this.add(`SAML_Custom_${ name }_mail_overwrite`, false, {
type: 'boolean',
i18nLabel: 'SAML_Custom_mail_overwrite',
});
this.add(`SAML_Custom_${ name }_logout_behaviour`, 'SAML', {
type: 'select',
values: [
{ key: 'SAML', i18nLabel: 'SAML_Custom_Logout_Behaviour_Terminate_SAML_Session' },
{ key: 'Local', i18nLabel: 'SAML_Custom_Logout_Behaviour_End_Only_RocketChat' },
],
i18nLabel: 'SAML_Custom_Logout_Behaviour',
});
this.add(`SAML_Custom_${ name }_channels_update`, false, {
type: 'boolean',
i18nLabel: 'SAML_Custom_channels_update',
i18nDescription: 'SAML_Custom_channels_update_description',
});
this.add(`SAML_Custom_${ name }_include_private_channels_update`, false, {
type: 'boolean',
i18nLabel: 'SAML_Custom_include_private_channels_update',
i18nDescription: 'SAML_Custom_include_private_channels_update_description',
});
settings.add(`SAML_Custom_${ name }_LogoutResponse_template`, defaultLogoutResponseTemplate, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_LogoutResponse_Template',
i18nDescription: 'SAML_LogoutResponse_Template_Description',
multiline: true,
});
this.add(`SAML_Custom_${ name }_default_user_role`, 'user', {
type: 'string',
i18nLabel: 'SAML_Default_User_Role',
i18nDescription: 'SAML_Default_User_Role_Description',
});
settings.add(`SAML_Custom_${ name }_LogoutRequest_template`, defaultLogoutRequestTemplate, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_LogoutRequest_Template',
i18nDescription: 'SAML_LogoutRequest_Template_Description',
multiline: true,
});
this.add(`SAML_Custom_${ name }_allowed_clock_drift`, false, {
type: 'int',
invalidValue: 0,
i18nLabel: 'SAML_Allowed_Clock_Drift',
i18nDescription: 'SAML_Allowed_Clock_Drift_Description',
});
});
settings.add(`SAML_Custom_${ name }_MetadataCertificate_template`, defaultMetadataCertificateTemplate, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_MetadataCertificate_Template',
i18nDescription: 'SAML_Metadata_Certificate_Template_Description',
multiline: true,
});
this.section('SAML_Section_5_Mapping', function() {
// Data Mapping Settings
this.add(`SAML_Custom_${ name }_user_data_fieldmap`, '{"username":"username", "email":"email", "name": "cn"}', {
type: 'string',
i18nLabel: 'SAML_Custom_user_data_fieldmap',
i18nDescription: 'SAML_Custom_user_data_fieldmap_description',
multiline: true,
});
});
});
settings.add(`SAML_Custom_${ name }_Metadata_template`, defaultMetadataTemplate, {
type: 'string',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_Metadata_Template',
i18nDescription: 'SAML_Metadata_Template_Description',
multiline: true,
SAMLUtils.events.emit('addSettings', name);
});
};

@ -6,8 +6,6 @@ import { loadSamlServiceProviders, addSettings } from './lib/settings';
import { Logger } from '../../logger/server';
import { SAMLUtils } from './lib/Utils';
settings.addGroup('SAML');
export const logger = new Logger('steffo:meteor-accounts-saml');
SAMLUtils.setLoggerInstance(logger);
@ -15,7 +13,7 @@ const updateServices = _.debounce(Meteor.bindEnvironment(() => {
loadSamlServiceProviders();
}), 2000);
settings.get(/^SAML_.+/, updateServices);
Meteor.startup(() => addSettings('Default'));
Meteor.startup(() => {
addSettings('Default');
settings.get(/^SAML_.+/, updateServices);
});

@ -9,8 +9,6 @@ export const serviceProviderOptions = {
customAuthnContext: 'Password',
authnContextComparison: 'Whatever',
defaultUserRole: 'user',
roleAttributeName: 'role',
roleAttributeSync: false,
allowedClockDrift: 0,
signatureValidationType: 'All',
identifierFormat: 'email',

@ -638,7 +638,6 @@ describe('SAML', () => {
};
globalSettings.userDataFieldMap = JSON.stringify(fieldMap);
globalSettings.roleAttributeName = 'roles';
SAMLUtils.updateGlobalSettings(globalSettings);
SAMLUtils.relayState = '[RelayState]';
@ -653,7 +652,7 @@ describe('SAML', () => {
expect(userObject).to.have.property('emailList').that.is.an('array').that.includes('testing@server.com');
expect(userObject).to.have.property('fullName').that.is.equal('[AnotherName]');
expect(userObject).to.have.property('username').that.is.equal('[AnotherUserName]');
expect(userObject).to.have.property('roles').that.is.an('array').with.members(['user', 'ruler', 'admin', 'king', 'president', 'governor', 'mayor']);
expect(userObject).to.have.property('roles').that.is.an('array').with.members(['user']);
expect(userObject).to.have.property('channels').that.is.an('array').with.members(['pets', 'pics', 'funny', 'random', 'babies']);
const map = new Map();
@ -738,37 +737,6 @@ describe('SAML', () => {
expect(userObject).to.have.property('username').that.is.equal('[username]');
});
it('should load multiple roles from the roleAttributeName when it has multiple values', () => {
const multipleRoles = {
...profile,
roles: ['role1', 'role2'],
};
const userObject = SAMLUtils.mapProfileToUserObject(multipleRoles);
expect(userObject).to.be.an('object').that.have.property('roles').that.is.an('array').with.members(['role1', 'role2']);
});
it('should assign the default role when the roleAttributeName is missing', () => {
const { globalSettings } = SAMLUtils;
globalSettings.roleAttributeName = '';
SAMLUtils.updateGlobalSettings(globalSettings);
const userObject = SAMLUtils.mapProfileToUserObject(profile);
expect(userObject).to.be.an('object').that.have.property('roles').that.is.an('array').with.members(['user']);
});
it('should assign the default role when the value of the role attribute is missing', () => {
const { globalSettings } = SAMLUtils;
globalSettings.roleAttributeName = 'inexistentField';
SAMLUtils.updateGlobalSettings(globalSettings);
const userObject = SAMLUtils.mapProfileToUserObject(profile);
expect(userObject).to.be.an('object').that.have.property('roles').that.is.an('array').with.members(['user']);
});
it('should run custom regexes when one is used', () => {
const { globalSettings } = SAMLUtils;
@ -1005,7 +973,6 @@ describe('SAML', () => {
};
globalSettings.userDataFieldMap = JSON.stringify(fieldMap);
globalSettings.roleAttributeName = 'roles';
SAMLUtils.updateGlobalSettings(globalSettings);
SAMLUtils.relayState = '[RelayState]';

@ -13,6 +13,7 @@ const bundles: IBundle = {
'push-privacy',
'scalability',
'teams-mention',
'saml-enterprise',
],
pro: [
],

@ -0,0 +1,2 @@
import './ldap';
import './saml';

@ -0,0 +1,44 @@
import { onLicense } from '../../app/license/server';
import type { ISAMLUser } from '../../../app/meteor-accounts-saml/server/definition/ISAMLUser';
import { SAMLUtils } from '../../../app/meteor-accounts-saml/server/lib/Utils';
import { settings } from '../../../app/settings/server';
import { addSettings } from '../settings/saml';
onLicense('saml-enterprise', () => {
SAMLUtils.events.on('mapUser', ({ profile, userObject }: { profile: Record<string, any>; userObject: ISAMLUser}) => {
const roleAttributeName = settings.get('SAML_Custom_Default_role_attribute_name') as string;
const roleAttributeSync = settings.get('SAML_Custom_Default_role_attribute_sync');
if (!roleAttributeSync) {
return;
}
if (roleAttributeName && profile[roleAttributeName]) {
let value = profile[roleAttributeName] || '';
if (typeof value === 'string') {
value = value.split(',');
}
userObject.roles = SAMLUtils.ensureArray<string>(value);
}
});
SAMLUtils.events.on('loadConfigs', (service: string, configs: Record<string, any>): void => {
// Include ee settings on the configs object so that they can be copied to the login service too
Object.assign(configs, {
customAuthnContext: settings.get(`${ service }_custom_authn_context`),
authnContextComparison: settings.get(`${ service }_authn_context_comparison`),
identifierFormat: settings.get(`${ service }_identifier_format`),
nameIDPolicyTemplate: settings.get(`${ service }_NameId_template`),
authnContextTemplate: settings.get(`${ service }_AuthnContext_template`),
authRequestTemplate: settings.get(`${ service }_AuthRequest_template`),
logoutResponseTemplate: settings.get(`${ service }_LogoutResponse_template`),
logoutRequestTemplate: settings.get(`${ service }_LogoutRequest_template`),
metadataCertificateTemplate: settings.get(`${ service }_MetadataCertificate_template`),
metadataTemplate: settings.get(`${ service }_Metadata_template`),
});
});
});
// For setting creation we add the listener first because the event is emmited during startup
SAMLUtils.events.on('addSettings', (name: string): void => onLicense('saml-enterprise', () => addSettings(name)));

@ -11,5 +11,6 @@ import '../app/livechat-enterprise/server/index';
import '../app/settings/server/index';
import '../app/teams-mention/server/index';
import './api';
import './configuration/index';
import './local-services/ldap/service';
import './configuration/ldap';
import './settings/index';

@ -0,0 +1 @@
import './saml';

@ -0,0 +1,113 @@
import { settings } from '../../../app/settings/server';
import {
defaultAuthnContextTemplate,
defaultAuthRequestTemplate,
defaultLogoutResponseTemplate,
defaultLogoutRequestTemplate,
defaultNameIDTemplate,
defaultIdentifierFormat,
defaultAuthnContext,
defaultMetadataTemplate,
defaultMetadataCertificateTemplate,
} from '../../../app/meteor-accounts-saml/server/lib/constants';
export const addSettings = function(name: string): void {
settings.addGroup('SAML', function() {
this.set({
tab: 'SAML_Enterprise',
enterprise: true,
modules: ['saml-enterprise'],
}, function() {
this.section('SAML_Section_4_Roles', function() {
// Roles Settings
this.add(`SAML_Custom_${ name }_role_attribute_sync`, false, {
type: 'boolean',
i18nLabel: 'SAML_Role_Attribute_Sync',
i18nDescription: 'SAML_Role_Attribute_Sync_Description',
invalidValue: false,
});
this.add(`SAML_Custom_${ name }_role_attribute_name`, '', {
type: 'string',
i18nLabel: 'SAML_Role_Attribute_Name',
i18nDescription: 'SAML_Role_Attribute_Name_Description',
invalidValue: '',
});
});
this.section('SAML_Section_6_Advanced', function() {
this.add(`SAML_Custom_${ name }_identifier_format`, defaultIdentifierFormat, {
type: 'string',
invalidValue: defaultIdentifierFormat,
i18nLabel: 'SAML_Identifier_Format',
i18nDescription: 'SAML_Identifier_Format_Description',
});
this.add(`SAML_Custom_${ name }_NameId_template`, defaultNameIDTemplate, {
type: 'string',
invalidValue: defaultNameIDTemplate,
i18nLabel: 'SAML_NameIdPolicy_Template',
i18nDescription: 'SAML_NameIdPolicy_Template_Description',
multiline: true,
});
this.add(`SAML_Custom_${ name }_custom_authn_context`, defaultAuthnContext, {
type: 'string',
invalidValue: defaultAuthnContext,
i18nLabel: 'SAML_Custom_Authn_Context',
i18nDescription: 'SAML_Custom_Authn_Context_description',
});
this.add(`SAML_Custom_${ name }_authn_context_comparison`, 'exact', {
type: 'select',
values: [
{ key: 'better', i18nLabel: 'Better' },
{ key: 'exact', i18nLabel: 'Exact' },
{ key: 'maximum', i18nLabel: 'Maximum' },
{ key: 'minimum', i18nLabel: 'Minimum' },
],
invalidValue: 'exact',
i18nLabel: 'SAML_Custom_Authn_Context_Comparison',
});
this.add(`SAML_Custom_${ name }_AuthnContext_template`, defaultAuthnContextTemplate, {
type: 'string',
invalidValue: defaultAuthnContextTemplate,
i18nLabel: 'SAML_AuthnContext_Template',
i18nDescription: 'SAML_AuthnContext_Template_Description',
multiline: true,
});
this.add(`SAML_Custom_${ name }_AuthRequest_template`, defaultAuthRequestTemplate, {
type: 'string',
invalidValue: defaultAuthRequestTemplate,
i18nLabel: 'SAML_AuthnRequest_Template',
i18nDescription: 'SAML_AuthnRequest_Template_Description',
multiline: true,
});
this.add(`SAML_Custom_${ name }_LogoutResponse_template`, defaultLogoutResponseTemplate, {
type: 'string',
invalidValue: defaultLogoutResponseTemplate,
i18nLabel: 'SAML_LogoutResponse_Template',
i18nDescription: 'SAML_LogoutResponse_Template_Description',
multiline: true,
});
this.add(`SAML_Custom_${ name }_LogoutRequest_template`, defaultLogoutRequestTemplate, {
type: 'string',
invalidValue: defaultLogoutRequestTemplate,
i18nLabel: 'SAML_LogoutRequest_Template',
i18nDescription: 'SAML_LogoutRequest_Template_Description',
multiline: true,
});
this.add(`SAML_Custom_${ name }_MetadataCertificate_template`, defaultMetadataCertificateTemplate, {
type: 'string',
invalidValue: defaultMetadataCertificateTemplate,
i18nLabel: 'SAML_MetadataCertificate_Template',
i18nDescription: 'SAML_Metadata_Certificate_Template_Description',
multiline: true,
});
this.add(`SAML_Custom_${ name }_Metadata_template`, defaultMetadataTemplate, {
type: 'string',
invalidValue: defaultMetadataTemplate,
i18nLabel: 'SAML_Metadata_Template',
i18nDescription: 'SAML_Metadata_Template_Description',
multiline: true,
});
});
});
});
};

@ -3581,6 +3581,9 @@
"SAML_AuthnContext_Template_Description": "You can use any variable from the AuthnRequest Template here.\n\n To add additional authn contexts, duplicate the __AuthnContextClassRef__ tag and replace the __\\_\\_authnContext\\_\\___ variable with the new context.",
"SAML_AuthnRequest_Template": "AuthnRequest Template",
"SAML_AuthnRequest_Template_Description": "The following variables are available:\n- **\\_\\_newId\\_\\_**: Randomly generated id string\n- **\\_\\_instant\\_\\_**: Current timestamp\n- **\\_\\_callbackUrl\\_\\_**: The Rocket.Chat callback URL.\n- **\\_\\_entryPoint\\_\\_**: The value of the __Custom Entry Point__ setting.\n- **\\_\\_issuer\\_\\_**: The value of the __Custom Issuer__ setting.\n- **\\_\\_identifierFormatTag\\_\\_**: The contents of the __NameID Policy Template__ if a valid __Identifier Format__ is configured.\n- **\\_\\_identifierFormat\\_\\_**: The value of the __Identifier Format__ setting.\n- **\\_\\_authnContextTag\\_\\_**: The contents of the __AuthnContext Template__ if a valid __Custom Authn Context__ is configured.\n- **\\_\\_authnContextComparison\\_\\_**: The value of the __Authn Context Comparison__ setting.\n- **\\_\\_authnContext\\_\\_**: The value of the __Custom Authn Context__ setting.",
"SAML_Connection": "Connection",
"SAML_Enterprise": "Enterprise",
"SAML_General": "General",
"SAML_Custom_Authn_Context": "Custom Authn Context",
"SAML_Custom_Authn_Context_Comparison": "Authn Context Comparison",
"SAML_Custom_Authn_Context_description": "Leave this empty to omit the authn context from the request.\n\n To add multiple authn contexts, add the additional ones directly to the __AuthnContext Template__ setting.",

Loading…
Cancel
Save