Merge branch 'develop' into new/omnichannel-source-fields

pull/23090/head
Tiago Evangelista Pinto 4 years ago
commit 7fea2ad1fc
  1. 14
      .github/workflows/build_and_test.yml
  2. 2
      .husky/pre-push
  3. 2
      app/meteor-accounts-saml/server/definition/ISAMLGlobalSettings.ts
  4. 2
      app/meteor-accounts-saml/server/definition/IServiceProviderOptions.ts
  5. 16
      app/meteor-accounts-saml/server/lib/SAML.ts
  6. 45
      app/meteor-accounts-saml/server/lib/Utils.ts
  7. 2
      app/meteor-accounts-saml/server/lib/parsers/Response.ts
  8. 266
      app/meteor-accounts-saml/server/lib/settings.ts
  9. 8
      app/meteor-accounts-saml/server/startup.ts
  10. 2
      app/meteor-accounts-saml/tests/data.ts
  11. 35
      app/meteor-accounts-saml/tests/server.tests.ts
  12. 32
      app/models/server/models/Sessions.js
  13. 164
      app/models/server/models/Sessions.tests.js
  14. 14
      client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js
  15. 48
      client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
  16. 6
      ee/app/api-enterprise/server/index.js
  17. 1
      ee/app/license/server/bundles.ts
  18. 2
      ee/server/configuration/index.ts
  19. 44
      ee/server/configuration/saml.ts
  20. 3
      ee/server/index.js
  21. 1
      ee/server/settings/index.ts
  22. 113
      ee/server/settings/saml.ts
  23. 990
      package-lock.json
  24. 3
      package.json
  25. 4
      packages/rocketchat-i18n/i18n/en.i18n.json
  26. 182
      packages/rocketchat-i18n/i18n/es.i18n.json
  27. 8
      server/lib/logger/Logger.ts
  28. 10
      server/startup/serverRunning.js

@ -117,13 +117,6 @@ jobs:
- run: meteor npm run translation-check
- name: Launch MongoDB
uses: wbari/start-mongoDB@v0.2
with:
mongoDBVersion: "4.0"
- run: meteor npm run testunit
- run: meteor npm run typecheck
- name: Build Storybook to sanity check components
@ -192,7 +185,7 @@ jobs:
strategy:
matrix:
node-version: ["12.22.1"]
mongodb-version: ["3.4", "3.6", "4.0", "4.2"]
mongodb-version: ["3.6", "4.0", "4.2", "4.4","5.0"]
steps:
- name: Launch MongoDB
@ -249,7 +242,10 @@ jobs:
run: |
npm install
- name: Test
- name: Unit Test
run: npm run testunit
- name: E2E Test
env:
TEST_MODE: "true"
MONGO_URL: mongodb://localhost:27017/rocketchat

@ -1,2 +1,2 @@
meteor npm run lint && \
meteor npm run testunit -- --exclude app/models/server/models/Sessions.tests.js
meteor npm run testunit

@ -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,74 +135,48 @@ export const addSamlService = function(name: string): void {
};
export const addSettings = function(name: string): void {
settings.add(`SAML_Custom_${ name }`, false, {
settings.addGroup('SAML', function() {
this.set({
tab: 'SAML_Connection',
}, function() {
this.add(`SAML_Custom_${ name }`, false, {
type: 'boolean',
group: 'SAML',
i18nLabel: 'Accounts_OAuth_Custom_Enable',
});
settings.add(`SAML_Custom_${ name }_provider`, 'provider-name', {
this.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', {
this.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', {
this.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', {
this.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, {
this.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`, '', {
this.section('SAML_Section_2_Certificate', function() {
this.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`, '', {
this.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', {
this.add(`SAML_Custom_${ name }_signature_validation_type`, 'All', {
type: 'select',
values: [
{ key: 'Response', i18nLabel: 'SAML_Custom_signature_validation_response' },
@ -211,215 +184,110 @@ export const addSettings = function(name: string): void {
{ 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`, '', {
this.add(`SAML_Custom_${ name }_private_key`, '', {
type: 'string',
group: 'SAML',
section: 'SAML_Section_2_Certificate',
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',
});
});
this.section('SAML_Section_3_Behavior', function() {
// Settings to customize behavior
settings.add(`SAML_Custom_${ name }_generate_username`, false, {
this.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', {
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' },
],
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_Username_Normalize',
});
settings.add(`SAML_Custom_${ name }_immutable_property`, 'EMail', {
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' },
],
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_Immutable_Property',
});
settings.add(`SAML_Custom_${ name }_name_overwrite`, false, {
this.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, {
this.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', {
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' },
],
group: 'SAML',
section: 'SAML_Section_3_Behavior',
i18nLabel: 'SAML_Custom_Logout_Behaviour',
});
settings.add(`SAML_Custom_${ name }_channels_update`, false, {
this.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, {
this.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', {
this.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, {
this.add(`SAML_Custom_${ name }_allowed_clock_drift`, false, {
type: 'int',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
invalidValue: 0,
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.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,
});
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,
});
settings.add(`SAML_Custom_${ name }_LogoutResponse_template`, defaultLogoutResponseTemplate, {
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',
group: 'SAML',
section: 'SAML_Section_6_Advanced',
i18nLabel: 'SAML_LogoutResponse_Template',
i18nDescription: 'SAML_LogoutResponse_Template_Description',
i18nLabel: 'SAML_Custom_user_data_fieldmap',
i18nDescription: 'SAML_Custom_user_data_fieldmap_description',
multiline: true,
});
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,
});
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,
});
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);
Meteor.startup(() => {
addSettings('Default');
settings.get(/^SAML_.+/, updateServices);
Meteor.startup(() => addSettings('Default'));
});

@ -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]';

@ -20,10 +20,6 @@ export const aggregates = {
day: { $lte: day },
}],
},
}, {
$sort: {
_id: 1,
},
}, {
$project: {
userId: 1,
@ -51,6 +47,10 @@ export const aggregates = {
time: { $sum: '$time' },
sessions: { $sum: 1 },
},
}, {
$sort: {
time: -1,
},
}, {
$group: {
_id: {
@ -70,6 +70,10 @@ export const aggregates = {
},
},
},
}, {
$sort: {
_id: 1,
},
}, {
$project: {
_id: 0,
@ -183,6 +187,10 @@ export const aggregates = {
$sum: '$time',
},
},
}, {
$sort: {
time: -1,
},
}, {
$group: {
_id: 1,
@ -312,6 +320,10 @@ export const aggregates = {
$sum: '$devices.time',
},
},
}, {
$sort: {
time: -1,
},
}, {
$project: {
_id: 0,
@ -348,6 +360,10 @@ export const aggregates = {
$sum: '$devices.time',
},
},
}, {
$sort: {
time: -1,
},
}, {
$project: {
_id: 0,
@ -384,6 +400,10 @@ export const aggregates = {
$sum: '$devices.time',
},
},
}, {
$sort: {
time: -1,
},
}, {
$project: {
_id: 0,
@ -421,6 +441,10 @@ export const aggregates = {
$sum: '$devices.time',
},
},
}, {
$sort: {
time: -1,
},
}, {
$project: {
_id: 0,

@ -2,9 +2,10 @@
import assert from 'assert';
import { MongoMemoryServer } from 'mongodb-memory-server';
import './Sessions.mocks.js';
const mongoUnit = require('mongo-unit');
const { MongoClient } = require('mongodb');
const { aggregates } = require('./Sessions');
@ -238,25 +239,29 @@ const DATA = {
lastActivityAt: new Date('2019-05-03T02:59:59.999Z'),
}],
sessions_dates,
}; // require('./fixtures/testData.json')
};
describe('Sessions Aggregates', () => {
let db;
if (!process.env.MONGO_URL) {
before(function() {
let mongod;
before(async function() {
this.timeout(120000);
return mongoUnit.start({ version: '3.2.22' })
.then((testMongoUrl) => { process.env.MONGO_URL = testMongoUrl; });
const version = '5.0.0';
console.log(`Starting mongo version ${ version }`);
mongod = await MongoMemoryServer.create({ binary: { version } });
process.env.MONGO_URL = await mongod.getUri();
});
after(() => {
mongoUnit.stop();
after(async () => {
await mongod.stop();
});
}
before(async () => {
const client = await MongoClient.connect(process.env.MONGO_URL, { useUnifiedTopology: true });
console.log(`Connecting to mongo at ${ process.env.MONGO_URL }`);
const client = await MongoClient.connect(process.env.MONGO_URL, { useUnifiedTopology: true, useNewUrlParser: true });
db = client.db('test');
after(() => {
@ -267,6 +272,7 @@ describe('Sessions Aggregates', () => {
const sessions = db.collection('sessions');
const sessions_dates = db.collection('sessions_dates');
return Promise.all([
sessions.insertMany(DATA.sessions),
sessions_dates.insertMany(DATA.sessions_dates),
@ -276,14 +282,14 @@ describe('Sessions Aggregates', () => {
it('should have sessions_dates data saved', () => {
const collection = db.collection('sessions_dates');
return collection.find().toArray()
.then((docs) => assert.equal(docs.length, DATA.sessions_dates.length));
.then((docs) => assert.strictEqual(docs.length, DATA.sessions_dates.length));
});
it('should match sessions between 2018-12-11 and 2019-1-10', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 1, day: 10 });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
$and: [{
$or: [
{ year: { $gt: 2018 } },
@ -303,8 +309,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 31);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 31);
assert.deepStrictEqual(docs, [
{ _id: '2018-12-11', year: 2018, month: 12, day: 11 },
{ _id: '2018-12-12', year: 2018, month: 12, day: 12 },
{ _id: '2018-12-13', year: 2018, month: 12, day: 13 },
@ -344,7 +350,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 10 });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
year: 2019,
$and: [{
$or: [
@ -363,8 +369,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 31);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 31);
assert.deepStrictEqual(docs, [
{ _id: '2019-1-11', year: 2019, month: 1, day: 11 },
{ _id: '2019-1-12', year: 2019, month: 1, day: 12 },
{ _id: '2019-1-13', year: 2019, month: 1, day: 13 },
@ -404,7 +410,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 5, day: 31 });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
year: 2019,
month: 5,
day: { $gte: 1, $lte: 31 },
@ -414,8 +420,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 31);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 31);
assert.deepStrictEqual(docs, [
{ _id: '2019-5-1', year: 2019, month: 5, day: 1 },
{ _id: '2019-5-2', year: 2019, month: 5, day: 2 },
{ _id: '2019-5-3', year: 2019, month: 5, day: 3 },
@ -455,7 +461,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 4, day: 30 });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
year: 2019,
month: 4,
day: { $gte: 1, $lte: 30 },
@ -465,8 +471,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 30);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 30);
assert.deepStrictEqual(docs, [
{ _id: '2019-4-1', year: 2019, month: 4, day: 1 },
{ _id: '2019-4-2', year: 2019, month: 4, day: 2 },
{ _id: '2019-4-3', year: 2019, month: 4, day: 3 },
@ -505,7 +511,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 28 });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
year: 2019,
month: 2,
day: { $gte: 1, $lte: 28 },
@ -515,8 +521,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 28);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 28);
assert.deepStrictEqual(docs, [
{ _id: '2019-2-1', year: 2019, month: 2, day: 1 },
{ _id: '2019-2-2', year: 2019, month: 2, day: 2 },
{ _id: '2019-2-3', year: 2019, month: 2, day: 3 },
@ -553,7 +559,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 27 });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
year: 2019,
$and: [{
$or: [
@ -572,8 +578,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 31);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 31);
assert.deepStrictEqual(docs, [
{ _id: '2019-1-28', year: 2019, month: 1, day: 28 },
{ _id: '2019-1-29', year: 2019, month: 1, day: 29 },
{ _id: '2019-1-30', year: 2019, month: 1, day: 30 },
@ -612,36 +618,25 @@ describe('Sessions Aggregates', () => {
it('should have sessions data saved', () => {
const collection = db.collection('sessions');
return collection.find().toArray()
.then((docs) => assert.equal(docs.length, DATA.sessions.length));
.then((docs) => assert.strictEqual(docs.length, DATA.sessions.length));
});
it('should generate daily sessions', () => {
const collection = db.collection('sessions');
return aggregates.dailySessionsOfYesterday(collection, { year: 2019, month: 5, day: 2 }).toArray()
.then((docs) => {
.then(async (docs) => {
docs.forEach((doc) => {
doc._id = `${ doc.userId }-${ doc.year }-${ doc.month }-${ doc.day }`;
});
assert.equal(docs.length, 3);
assert.deepEqual(docs, [{
await collection.insertMany(docs);
assert.strictEqual(docs.length, 3);
assert.deepStrictEqual(docs, [{
_id: 'xPZXw9xqM3kKshsse-2019-5-2',
time: 5814,
sessions: 3,
devices: [{
sessions: 1,
time: 286,
device: {
type: 'browser',
name: 'Firefox',
longVersion: '66.0.3',
os: {
name: 'Linux',
version: '12',
},
version: '66.0.3',
},
}, {
sessions: 2,
time: 5528,
device: {
@ -654,6 +649,19 @@ describe('Sessions Aggregates', () => {
},
version: '73.0.3683',
},
}, {
sessions: 1,
time: 286,
device: {
type: 'browser',
name: 'Firefox',
longVersion: '66.0.3',
os: {
name: 'Linux',
version: '12',
},
version: '66.0.3',
},
}],
type: 'user_daily',
_computedAt: docs[0]._computedAt,
@ -713,8 +721,6 @@ describe('Sessions Aggregates', () => {
userId: 'xPZXw9xqM3kKshsse2',
mostImportantRole: 'admin',
}]);
return collection.insertMany(docs);
});
});
@ -722,8 +728,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 })
.then((docs) => {
assert.equal(docs.length, 1);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 1);
assert.deepStrictEqual(docs, [{
count: 2,
roles: [{
count: 1,
@ -746,8 +752,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueUsersOfYesterday(collection, { year: 2019, month: 5, day: 1 })
.then((docs) => {
assert.equal(docs.length, 1);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 1);
assert.deepStrictEqual(docs, [{
count: 1,
roles: [{
count: 1,
@ -765,8 +771,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueUsersOfYesterday(collection, { year: 2019, month: 5, day: 2 })
.then((docs) => {
assert.equal(docs.length, 1);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 1);
assert.deepStrictEqual(docs, [{
count: 1,
roles: [{
count: 1,
@ -784,8 +790,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueDevicesOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 })
.then((docs) => {
assert.equal(docs.length, 2);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 2);
assert.deepStrictEqual(docs, [{
count: 3,
time: 9695,
type: 'browser',
@ -805,8 +811,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueDevicesOfYesterday(collection, { year: 2019, month: 5, day: 2 })
.then((docs) => {
assert.equal(docs.length, 2);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 2);
assert.deepStrictEqual(docs, [{
count: 2,
time: 5528,
type: 'browser',
@ -826,8 +832,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueOSOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 })
.then((docs) => {
assert.equal(docs.length, 2);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 2);
assert.deepStrictEqual(docs, [{
count: 3,
time: 9695,
name: 'Mac OS',
@ -845,8 +851,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueOSOfYesterday(collection, { year: 2019, month: 5, day: 2 })
.then((docs) => {
assert.equal(docs.length, 2);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 2);
assert.deepStrictEqual(docs, [{
count: 2,
time: 5528,
name: 'Mac OS',
@ -864,7 +870,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 1, day: 4, type: 'week' });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
$and: [{
$or: [
{ year: { $gt: 2018 } },
@ -884,8 +890,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 7);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 7);
assert.deepStrictEqual(docs, [
{ _id: '2018-12-29', year: 2018, month: 12, day: 29 },
{ _id: '2018-12-30', year: 2018, month: 12, day: 30 },
{ _id: '2018-12-31', year: 2018, month: 12, day: 31 },
@ -901,7 +907,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 4, type: 'week' });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
year: 2019,
$and: [{
$or: [
@ -920,8 +926,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 7);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 7);
assert.deepStrictEqual(docs, [
{ _id: '2019-1-29', year: 2019, month: 1, day: 29 },
{ _id: '2019-1-30', year: 2019, month: 1, day: 30 },
{ _id: '2019-1-31', year: 2019, month: 1, day: 31 },
@ -937,7 +943,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 5, day: 7, type: 'week' });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
year: 2019,
month: 5,
day: { $gte: 1, $lte: 7 },
@ -947,8 +953,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 7);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 7);
assert.deepStrictEqual(docs, [
{ _id: '2019-5-1', year: 2019, month: 5, day: 1 },
{ _id: '2019-5-2', year: 2019, month: 5, day: 2 },
{ _id: '2019-5-3', year: 2019, month: 5, day: 3 },
@ -964,7 +970,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions_dates');
const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 5, day: 14, type: 'week' });
assert.deepEqual($match, {
assert.deepStrictEqual($match, {
year: 2019,
month: 5,
day: { $gte: 8, $lte: 14 },
@ -974,8 +980,8 @@ describe('Sessions Aggregates', () => {
$match,
}]).toArray()
.then((docs) => {
assert.equal(docs.length, 7);
assert.deepEqual(docs, [
assert.strictEqual(docs.length, 7);
assert.deepStrictEqual(docs, [
{ _id: '2019-5-8', year: 2019, month: 5, day: 8 },
{ _id: '2019-5-9', year: 2019, month: 5, day: 9 },
{ _id: '2019-5-10', year: 2019, month: 5, day: 10 },
@ -991,7 +997,7 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31, type: 'week' })
.then((docs) => {
assert.equal(docs.length, 0);
assert.strictEqual(docs.length, 0);
});
});
@ -999,8 +1005,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7, type: 'week' })
.then((docs) => {
assert.equal(docs.length, 1);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 1);
assert.deepStrictEqual(docs, [{
count: 2,
roles: [{
count: 1,
@ -1023,8 +1029,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueDevicesOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7, type: 'week' })
.then((docs) => {
assert.equal(docs.length, 2);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 2);
assert.deepStrictEqual(docs, [{
count: 3,
time: 9695,
type: 'browser',
@ -1044,8 +1050,8 @@ describe('Sessions Aggregates', () => {
const collection = db.collection('sessions');
return aggregates.getUniqueOSOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7 })
.then((docs) => {
assert.equal(docs.length, 2);
assert.deepEqual(docs, [{
assert.strictEqual(docs.length, 2);
assert.deepStrictEqual(docs, [{
count: 3,
time: 9695,
name: 'Mac OS',

@ -8,11 +8,11 @@ import { isEmail } from '../../../../../../app/utils/client';
import CustomFieldsForm from '../../../../../components/CustomFieldsForm';
import VerticalBar from '../../../../../components/VerticalBar';
import { createToken } from '../../../../../components/helpers';
import { useEndpoint } from '../../../../../contexts/ServerContext';
import { useToastMessageDispatch } from '../../../../../contexts/ToastMessagesContext';
import { useTranslation } from '../../../../../contexts/TranslationContext';
import { AsyncStatePhase } from '../../../../../hooks/useAsyncState';
import { useComponentDidUpdate } from '../../../../../hooks/useComponentDidUpdate';
import { useEndpointAction } from '../../../../../hooks/useEndpointAction';
import { useEndpointData } from '../../../../../hooks/useEndpointData';
import { useForm } from '../../../../../hooks/useForm';
import { formsSubscription } from '../../../additionalForms';
@ -107,15 +107,9 @@ function ContactNewEdit({ id, data, close }) {
[allCustomFields],
);
const saveContact = useEndpointAction('POST', 'omnichannel/contact');
const emailAlreadyExistsAction = useEndpointAction(
'GET',
`omnichannel/contact.search?email=${email}`,
);
const phoneAlreadyExistsAction = useEndpointAction(
'GET',
`omnichannel/contact.search?phone=${phone}`,
);
const saveContact = useEndpoint('POST', 'omnichannel/contact');
const emailAlreadyExistsAction = useEndpoint('GET', `omnichannel/contact.search?email=${email}`);
const phoneAlreadyExistsAction = useEndpoint('GET', `omnichannel/contact.search?phone=${phone}`);
const checkEmailExists = useMutableCallback(async () => {
if (!isEmail(email)) {

@ -17,6 +17,7 @@ import { useSetModal } from '../../../../../../contexts/ModalContext';
import { useOmnichannelRouteConfig } from '../../../../../../contexts/OmnichannelContext';
import { useEndpoint, useMethod } from '../../../../../../contexts/ServerContext';
import { useSetting } from '../../../../../../contexts/SettingsContext';
import { useToastMessageDispatch } from '../../../../../../contexts/ToastMessagesContext';
import { useTranslation } from '../../../../../../contexts/TranslationContext';
import { useUserId } from '../../../../../../contexts/UserContext';
import { handleError } from '../../../../../../lib/utils/handleError';
@ -33,13 +34,13 @@ export const useQuickActions = (
const setModal = useSetModal();
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const context = useQuickActionsContext();
const actions = (Array.from(context.actions.values()) as QuickActionsActionConfig[]).sort(
(a, b) => (a.order || 0) - (b.order || 0),
);
const [onHoldModalActive, setOnHoldModalActive] = useState(false);
const [email, setEmail] = useState('');
const visitorRoomId = room.v._id;
const rid = room._id;
@ -52,18 +53,16 @@ export const useQuickActions = (
if (!visitorRoomId) {
return;
}
const {
visitor: { visitorEmails },
} = await getVisitorInfo({ visitorId: visitorRoomId });
if (visitorEmails?.length && visitorEmails[0].address) {
setEmail(visitorEmails[0].address);
return visitorEmails[0].address;
}
});
useEffect(() => {
getVisitorEmail();
}, [visitorRoomId, getVisitorEmail]);
useEffect(() => {
if (onHoldModalActive && roomLastMessage?.token) {
setModal(null);
@ -85,7 +84,7 @@ export const useQuickActions = (
closeModal();
Session.set('openedRoom', null);
FlowRouter.go('/home');
} catch (error) {
} catch (error: any) {
handleError(error);
}
}, [closeModal, methodReturn, rid]);
@ -99,7 +98,7 @@ export const useQuickActions = (
closeModal();
RoomManager.close(`l${rid}`);
toastr.success(t('Livechat_transcript_has_been_requested'));
} catch (error) {
} catch (error: any) {
handleError(error);
}
},
@ -113,7 +112,7 @@ export const useQuickActions = (
try {
await sendTranscript(token, rid, email, subject);
closeModal();
} catch (error) {
} catch (error: any) {
handleError(error);
}
},
@ -127,7 +126,7 @@ export const useQuickActions = (
await discardTranscript(rid);
toastr.success(t('Livechat_transcript_request_has_been_canceled'));
closeModal();
} catch (error) {
} catch (error: any) {
handleError(error);
}
}, [closeModal, discardTranscript, rid, t]);
@ -168,7 +167,7 @@ export const useQuickActions = (
toastr.success(t('Transferred'));
FlowRouter.go('/');
closeModal();
} catch (error) {
} catch (error: any) {
handleError(error);
}
},
@ -183,7 +182,7 @@ export const useQuickActions = (
await closeChat(rid, comment, { clientAction: true, tags });
closeModal();
toastr.success(t('Chat_closed_successfully'));
} catch (error) {
} catch (error: any) {
handleError(error);
}
},
@ -197,21 +196,28 @@ export const useQuickActions = (
await onHoldChat({ roomId: rid });
closeModal();
toastr.success(t('Chat_On_Hold_Successfully'));
} catch (error) {
} catch (error: any) {
handleError(error);
}
}, [onHoldChat, closeModal, rid, t]);
const openModal = useMutableCallback((id: string) => {
const openModal = useMutableCallback(async (id: string) => {
switch (id) {
case QuickActionsEnum.MoveQueue:
setModal(<ReturnChatQueueModal onMoveChat={handleMoveChat} onCancel={closeModal} />);
break;
case QuickActionsEnum.Transcript:
const visitorEmail = await getVisitorEmail();
if (!visitorEmail) {
dispatchToastMessage({ type: 'error', message: t('Customer_without_registered_email') });
break;
}
setModal(
<TranscriptModal
room={room}
email={email}
email={visitorEmail}
onRequest={handleRequestTranscript}
onSend={handleSendTranscript}
onDiscard={handleDiscardTranscript}
@ -258,17 +264,11 @@ export const useQuickActions = (
room?.open &&
(room.u?._id === uid || hasManagerRole) &&
room?.lastMessage?.t !== 'livechat-close';
const canMoveQueue = !!omnichannelRouteConfig?.returnQueue && room?.u !== undefined;
const canForwardGuest = usePermission('transfer-livechat-guest');
const canSendTranscript = usePermission('send-omnichannel-chat-transcript');
const canCloseOthersRoom = usePermission('close-others-livechat-room');
const canCloseRoom = usePermission('close-livechat-room');
const canMoveQueue = !!omnichannelRouteConfig?.returnQueue && room?.u !== undefined;
const canCloseOthersRoom = usePermission('close-others-livechat-room');
const canPlaceChatOnHold = Boolean(
!room.onHold && room.u && !(room as any).lastMessage?.token && manualOnHoldAllowed,
);
@ -280,7 +280,7 @@ export const useQuickActions = (
case QuickActionsEnum.ChatForward:
return !!roomOpen && canForwardGuest;
case QuickActionsEnum.Transcript:
return !!email && canSendTranscript;
return canSendTranscript;
case QuickActionsEnum.CloseChat:
return !!roomOpen && (canCloseRoom || canCloseOthersRoom);
case QuickActionsEnum.OnHoldChat:

@ -1 +1,5 @@
import './canned-responses';
import { onLicense } from '../../license/server';
onLicense('canned-responses', async () => {
await import('./canned-responses');
});

@ -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,
});
});
});
});
};

990
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -105,6 +105,7 @@
"@types/xmldom": "^0.1.30",
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^3.0.0",
"@types/uuid": "^8.3.1",
"autoprefixer": "^9.8.6",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
@ -129,7 +130,6 @@
"jsdom-global": "3.0.2",
"mocha": "^9.0.0",
"mock-require": "^3.0.3",
"mongo-unit": "^2.0.1",
"pino-pretty": "^7.0.0",
"postcss": "^8.3.5",
"postcss-custom-properties": "^11.0.0",
@ -251,6 +251,7 @@
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
"mongodb": "^3.6.9",
"mongodb-memory-server": "^7.4.1",
"nats": "^1.4.8",
"node-dogstatsd": "^0.0.7",
"node-gcm": "1.0.0",

@ -1315,6 +1315,7 @@
"Custom_User_Status_Has_Been_Deleted": "Custom User Status Has Been Deleted",
"Custom_User_Status_Info": "Custom User Status Info",
"Custom_User_Status_Updated_Successfully": "Custom User Status Updated Successfully",
"Customer_without_registered_email": "The customer does not have a registered email address",
"Customize": "Customize",
"CustomSoundsFilesystem": "Custom Sounds Filesystem",
"Daily_Active_Users": "Daily Active Users",
@ -3583,6 +3584,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.",

@ -20,8 +20,8 @@
"A_new_owner_will_be_assigned_automatically_to_the__roomName__room": "Un nuevo propietario será asignado automáticamente a la sala <span style=\"font-weight: bold;\">__roomName__</span>.",
"A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__": "Un nuevo propietario será asignado automáticamente a estas <span style=\"font-weight: bold;\">__count__</span> salas. <br/> __rooms__.",
"Accept": "Aceptar",
"Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Aceptar solicitudes entrantes de LiveChat aunque no haya agentes en línea",
"Accept_new_livechats_when_agent_is_idle": "Aceptar nuevas solicitudes de LiveChat cuando el agente esté inactivo",
"Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Aceptar solicitudes entrantes de Omnichannel aunque no haya agentes en línea",
"Accept_new_livechats_when_agent_is_idle": "Aceptar nuevas solicitudes de Omnichannel cuando el agente esté inactivo",
"Accept_with_no_online_agents": "Aceptar sin agentes en línea",
"Access_not_authorized": "Acceso no autorizado",
"Access_Token_URL": "URL de Token de Acceso",
@ -270,8 +270,8 @@
"Add_User": "Añadir usuario",
"Add_users": "Añadir usuarios",
"Add_members": "Añadir miembros",
"add-livechat-department-agents": "Añadir agentes de LiveChat a departamentos",
"add-livechat-department-agents_description": "Permiso para agregar agentes Livechat a los departamentos",
"add-livechat-department-agents": "Añadir agentes de Omnichannel a departamentos",
"add-livechat-department-agents_description": "Permiso para agregar agentes Omnichannel a los departamentos",
"add-oauth-service": "Agregar Servicio Oauth",
"add-oauth-service_description": "Permiso para agregar un nuevo servicio OAuth",
"add-user": "Añadir Usuario",
@ -318,7 +318,7 @@
"All_users": "Todos los usuarios",
"All_users_in_the_channel_can_write_new_messages": "Todos los usuarios del canal pueden escribir nuevos mensajes",
"Allow_collect_and_store_HTTP_header_informations": "Permitir recopilar y almacenar información de encabezado HTTP",
"Allow_collect_and_store_HTTP_header_informations_description": "Esta configuración determina si Livechat puede almacenar información recopilada de los datos del encabezado HTTP, como la dirección IP, el Agente de usuario, etc.",
"Allow_collect_and_store_HTTP_header_informations_description": "Esta configuración determina si Omnichannel puede almacenar información recopilada de los datos del encabezado HTTP, como la dirección IP, el Agente de usuario, etc.",
"Allow_Invalid_SelfSigned_Certs": "Permitir Certificados Auto-Firmados Invalidos",
"Allow_Invalid_SelfSigned_Certs_Description": "Permitir certificados SSL autofirmados e invalidos para validación de enlaces y vistas previas.",
"Allow_Marketing_Emails": "Permitir correos electrónicos de marketing",
@ -498,17 +498,17 @@
"Apps_Permissions_room_write": "Modificar la información de las salas",
"Apps_Permissions_message_read": "Leer mensajes",
"Apps_Permissions_message_write": "Enviar y modificar mensajes",
"Apps_Permissions_livechat-status_read": "Acceder a la información de estado de Livechat",
"Apps_Permissions_livechat-custom-fields_write": "Modificar la configuración de los campos personalizados de Livechat",
"Apps_Permissions_livechat-visitor_read": "Acceder a la información de los visitantes de Livechat",
"Apps_Permissions_livechat-visitor_write": "Modificar la información de los visitantes de Livechat",
"Apps_Permissions_livechat-message_read": "Acceder a la información de los mensajes de Livechat",
"Apps_Permissions_livechat-message_write": "Modificar la información de los mensajes de Livechat",
"Apps_Permissions_livechat-room_read": "Acceder a la información de la sala Livechat",
"Apps_Permissions_livechat-room_write": "Modificar la información de la sala Livechat",
"Apps_Permissions_livechat-department_read": "Acceder a la información del departamento de Livechat",
"Apps_Permissions_livechat-department_multiple": "Acceso a la información de varios departamentos de Livechat",
"Apps_Permissions_livechat-department_write": "Modificar la información del departamento de Livechat",
"Apps_Permissions_livechat-status_read": "Acceder a la información de estado de Omnichannel",
"Apps_Permissions_livechat-custom-fields_write": "Modificar la configuración de los campos personalizados de Omnichannel",
"Apps_Permissions_livechat-visitor_read": "Acceder a la información de los visitantes de Omnichannel",
"Apps_Permissions_livechat-visitor_write": "Modificar la información de los visitantes de Omnichannel",
"Apps_Permissions_livechat-message_read": "Acceder a la información de los mensajes de Omnichannel",
"Apps_Permissions_livechat-message_write": "Modificar la información de los mensajes de Omnichannel",
"Apps_Permissions_livechat-room_read": "Acceder a la información de la sala Omnichannel",
"Apps_Permissions_livechat-room_write": "Modificar la información de la sala Omnichannel",
"Apps_Permissions_livechat-department_read": "Acceder a la información del departamento de Omnichannel",
"Apps_Permissions_livechat-department_multiple": "Acceso a la información de varios departamentos de Omnichannel",
"Apps_Permissions_livechat-department_write": "Modificar la información del departamento de Omnichannel",
"Apps_Permissions_slashcommand": "Registrar nuevos comandos de barra",
"Apps_Permissions_api": "Registrar nuevos puntos finales HTTP",
"Apps_Permissions_env_read": "Acceda a información mínima sobre este entorno de servidor",
@ -708,7 +708,7 @@
"Canned_Response_Delete_Warning": "La eliminación de una respuesta automática no se puede deshacer.",
"Canned_Response_Removed": "Respuesta predefinida eliminada",
"Canned_Response_Sharing_Department_Description": "Cualquier persona del departamento seleccionado puede acceder a esta respuesta predefinida",
"Canned_Response_Sharing_Private_Description": "Solo usted y los administradores de Livechat pueden acceder a esta respuesta predefinida",
"Canned_Response_Sharing_Private_Description": "Solo usted y los administradores de Omnichannel pueden acceder a esta respuesta predefinida",
"Canned_Response_Sharing_Public_Description": "Cualquiera puede acceder a esta respuesta predefinida",
"Create_your_First_Canned_Response": "Crea tu primera respuesta predefinida",
"Canned_Responses_Enable": "Habilitar respuesta predefinida",
@ -742,8 +742,8 @@
"CDN_PREFIX": "Prefijo de CDN",
"CDN_PREFIX_ALL": "Utilizar prefijo CDN para todos los activos",
"Certificates_and_Keys": "Certificados y Claves",
"change-livechat-room-visitor": "Cambiar visitantes de LiveChat Room",
"change-livechat-room-visitor_description": "Permiso para agregar información adicional al visitante de Livechat",
"change-livechat-room-visitor": "Cambiar visitante de sala Omnichannel",
"change-livechat-room-visitor_description": "Permiso para agregar información adicional al visitante de Omnichannel",
"Change_Room_Type": "Cambiar el Tipo de Sala",
"Changing_email": "Cambiando correo electrónico",
"channel": "canal",
@ -868,10 +868,10 @@
"Close": "Cerrar",
"Close_chat": "Cerrar chat",
"Close_room_description": "Estás a punto de cerrar este chat. ¿Estás seguro de que quieres continuar?",
"close-livechat-room": "Cerrar la sala de LiveChat",
"close-livechat-room_description": "Permiso para cerrar el canal LiveChat actual",
"close-others-livechat-room": "Cerrar otra sala de LiveChat",
"close-others-livechat-room_description": "Permiso para cerrar otros canales de LiveChat",
"close-livechat-room": "Cerrar la sala de Omnichannel",
"close-livechat-room_description": "Permiso para cerrar el canal Omnichannel actual",
"close-others-livechat-room": "Cerrar otra sala de Omnichannel",
"close-others-livechat-room_description": "Permiso para cerrar otros canales de Omnichannel",
"Closed": "Cerrado",
"Closed_At": "Cerrado en",
"Closed_automatically": "Cerrado automáticamente por el sistema",
@ -969,7 +969,7 @@
"Contact_Info": "Información de contacto",
"Content": "Contenido",
"Continue": "Continuar",
"Continuous_sound_notifications_for_new_livechat_room": "Notificaciones continuas de sonido para la nueva sala de LiveChat",
"Continuous_sound_notifications_for_new_livechat_room": "Notificaciones continuas de sonido para la nueva sala de Omnichannel",
"Conversation": "Conversación",
"Conversation_closed": "Conversación cerrada: __comment__.",
"Conversation_closing_tags": "Etiquetas de cierre de la conversación",
@ -1455,7 +1455,7 @@
"Domain_added": "dominio Agregado",
"Domain_removed": "Dominio Borrado",
"Domains": "Dominios",
"Domains_allowed_to_embed_the_livechat_widget": "Lista de dominios separados por comas que permite incrustar el widget de LiveChat. Déjelo en blanco para permitir todos los dominios.",
"Domains_allowed_to_embed_the_livechat_widget": "Lista de dominios separados por comas que permite incrustar el widget LiveChat. Déjelo en blanco para permitir todos los dominios.",
"Dont_ask_me_again": "¡No me preguntes otra vez!",
"Dont_ask_me_again_list": "No me preguntes de nuevo",
"Download": "Descargar",
@ -1506,8 +1506,8 @@
"Edit_Trigger": "Editar disparador",
"Edit_Unit": "Editar unidad",
"Edit_User": "Editar usuario",
"edit-livechat-room-customfields": "Editar campos personalizados de Livechat Rom",
"edit-livechat-room-customfields_description": "Permiso para editar los campos personalizados de la sala Livechat ",
"edit-livechat-room-customfields": "Editar campos personalizados de sala Omnichannel",
"edit-livechat-room-customfields_description": "Permiso para editar los campos personalizados de la sala Omnichannel ",
"edit-message": "Editar Mensaje",
"edit-message_description": "Permiso para editar un mensaje dentro de una sala",
"edit-other-user-active-status": "Editar el estado activo de otro usuario",
@ -1530,8 +1530,8 @@
"edit-room-avatar_description": "Permiso para editar el avatar de una sala",
"edit-room-retention-policy": "Editar la política de retención de la sala",
"edit-room-retention-policy_description": "Permiso para editar la política de retención de una habitación, para eliminar automáticamente los mensajes en ella",
"edit-omnichannel-contact": "Editar contacto Livechat",
"edit-omnichannel-contact_description": "Permiso para editar el contacto Livechat",
"edit-omnichannel-contact": "Editar contacto Omnichannel",
"edit-omnichannel-contact_description": "Permiso para editar el contacto Omnichannel",
"Edit_Contact_Profile": "Editar perfil del contacto",
"edited": "Editado",
"Editing_room": "Editando sala",
@ -1626,7 +1626,7 @@
"Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances": "Error: Rocket.Chat requiere oplog tail cuando se ejecuta en varias instancias",
"Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances_details": "Asegúrese de que su MongoDB esté en modo ReplicaSet y que la variable de entorno MONGO_OPLOG_URL esté definida correctamente en el servidor de aplicaciones.",
"Error_sending_livechat_offline_message": "Error al enviar el mensaje LiveChat de fuera de línea",
"Error_sending_livechat_transcript": "Error al enviar la transcripción de LiveChat",
"Error_sending_livechat_transcript": "Error al enviar la transcripción de sala Omnichannel",
"Error_Site_URL": "Site_Url no válida",
"Error_Site_URL_description": "Por favor, actualice el valor de configuración \"Site_Url\", encuentre más información <a target=\"_blank\" href=\"https://go.rocket.chat/i/invalid-site-url\">aquí",
"error-action-not-allowed": "__action__ no está permitido",
@ -1746,7 +1746,7 @@
"error-starring-message": "No se pudo mirar el mensaje",
"error-tags-must-be-assigned-before-closing-chat": "Se deben asignar etiquetas antes de cerrar el chat",
"error-the-field-is-required": " E campo __field__. es requerido",
"error-this-is-not-a-livechat-room": "Esta no es una sala de LiveChat",
"error-this-is-not-a-livechat-room": "Esta no es una sala de Omnichannel",
"error-token-already-exists": "Ya existe un token con este nombre",
"error-token-does-not-exists": "El token no existe",
"error-too-many-requests": "Error, demasiadas peticiones. Por favor más despacio. Debe esperar __seconds__ segundos antes de volver a intentarlo.",
@ -1754,7 +1754,7 @@
"error-unpinning-message": "No se pudo desanclar el mensaje",
"error-user-has-no-roles": "El usuario no tiene roles",
"error-user-is-not-activated": "El usuario no ha sido activado",
"error-user-is-not-agent": "El usuario no es un agente de LiveChat",
"error-user-is-not-agent": "El usuario no es un agente de Omnichannel",
"error-user-is-offline": "El usuario está fuera de línea",
"error-user-limit-exceeded": "La cantidad de usuarios a los que intenta invitar #channel_name supera el límite establecido por el administrador",
"error-user-not-belong-to-department": "El usuario no pertenece a este departamento",
@ -2177,7 +2177,7 @@
"Inbox_Info": "Información de la bandeja de entrada",
"Include_Offline_Agents": "Incluir agentes fuera de línea",
"Inclusive": "Inclusivo",
"Incoming_Livechats": "LiveChats entrantes",
"Incoming_Livechats": "Chats Omnichannel entrantes",
"Incoming_WebHook": "WebHook entrante",
"Industry": "Industria",
"Info": "Información",
@ -2521,7 +2521,7 @@
"Leave": "Salir",
"Leave_a_comment": "Dejar un comentario",
"Leave_Group_Warning": "¿Seguro que quieres dejar el grupo \"%s\"?",
"Leave_Livechat_Warning": "¿Seguro que quieres salir del LiveChat con \"%s\"?",
"Leave_Livechat_Warning": "¿Seguro que quieres salir de la sala Omnichannel con \"%s\"?",
"Leave_Private_Warning": "¿Seguro que quieres salir de la discusión con \"%s\"?",
"Leave_room": "Salir de la sala",
"Leave_Room_Warning": "¿Seguro que quieres salir de la sala \"%s\"?",
@ -2541,7 +2541,7 @@
"Livechat": "LiveChat",
"Livechat_abandoned_rooms_action": "Cómo gestionar el abandono de visitantes",
"Livechat_abandoned_rooms_closed_custom_message": "Mensaje personalizado cuando la sala se cierra automáticamente por inactividad del visitante",
"Livechat_agents": "Agentes de LiveChat",
"Livechat_agents": "Agentes de Omnichannel",
"Livechat_Agents": "# de Agentes",
"Livechat_allow_manual_on_hold": "Permitir a los agentes poner manualmente el chat en espera",
"Livechat_allow_manual_on_hold_Description": "Si está habilitado, el agente obtendrá una nueva opción para poner un chat en espera, siempre que el agente haya enviado el último mensaje.",
@ -2558,26 +2558,26 @@
"Livechat_close_chat": "Cerrar chat",
"Livechat_custom_fields_options_placeholder": "Lista separada por comas que se utiliza para seleccionar un valor preconfigurado. No se aceptan espacios entre elementos.",
"Livechat_custom_fields_public_description": "Los campos personalizados públicos se mostrarán en aplicaciones externas, como Livechat, etc.",
"Livechat_Dashboard": "Panel LiveChat",
"Livechat_DepartmentOfflineMessageToChannel": "Enviar mensajes de LiveChat sin conexión de este departamento a un canal",
"Livechat_Dashboard": "Panel Omnichannel",
"Livechat_DepartmentOfflineMessageToChannel": "Enviar mensajes de Omnichannel sin conexión de este departamento a un canal",
"Livechat_enable_message_character_limit": "Habilitar el límite de caracteres del mensaje",
"Livechat_enabled": "LiveChat habilitado",
"Livechat_enabled": "Omnichannel habilitado",
"Livechat_Facebook_API_Key": "Clave API de LiveChat ",
"Livechat_Facebook_API_Secret": "API Secreto LiveChat ",
"Livechat_Facebook_Enabled": "Integración de Facebook habilitada",
"Livechat_forward_open_chats": "Reenviar charlas abiertas",
"Livechat_forward_open_chats_timeout": "Tiempo de espera (en segundos) para reenviar los chats",
"Livechat_guest_count": "Contador de invitados",
"Livechat_Inquiry_Already_Taken": "Solicitud de LiveChat ya atendida",
"Livechat_Inquiry_Already_Taken": "Solicitud de Omnichannel ya atendida",
"Livechat_Installation": "Instalación de LiveChat",
"Livechat_last_chatted_agent_routing": "Agente preferido en el último chat",
"Livechat_last_chatted_agent_routing_Description": "La configuración del último agente con el que conversó asigna chats al agente que interactuó anteriormente con el mismo visitante si el agente está disponible cuando se inicia el chat.",
"Livechat_managers": "Supervisores de LiveChat",
"Livechat_managers": "Supervisores de Omnichannel",
"Livechat_Managers": "Administradores",
"Livechat_max_queue_wait_time_action": "Cómo manejar los chats en cola cuando se alcanza el tiempo máximo de espera",
"Livechat_maximum_queue_wait_time": "Tiempo máximo de espera en cola",
"Livechat_message_character_limit": "Límite de caracteres del mensaje de LiveChat",
"Livechat_monitors": "Monitores de LiveChat",
"Livechat_monitors": "Monitores de Omnichannel",
"Livechat_Monitors": "Monitores",
"Livechat_offline": "LiveChat desconectado",
"Livechat_offline_message_sent": "Mensaje de Livechat enviado sin conexión ",
@ -2590,19 +2590,19 @@
"Omnichannel_onHold_Chat": "Poner chat en espera",
"Livechat_online": "LiveChat en línea",
"Omnichannel_placed_chat_on_hold": "Chat en espera: __comment__",
"Livechat_Queue": "Cola de LiveChat",
"Livechat_Queue": "Cola de Omnichannel",
"Livechat_registration_form": "Formulario de Registro",
"Livechat_registration_form_message": "Mensaje del formulario de registro",
"Livechat_room_count": "Recuento de Room LiveChat",
"Livechat_Routing_Method": "Método de enrutamiento del LiveChat",
"Livechat_status": "Estado de LiveChat",
"Livechat_room_count": "Recuento de salas Omnichannel",
"Livechat_Routing_Method": "Método de enrutamiento del Omnichannel",
"Livechat_status": "Estado de Omnichannel",
"Livechat_Take_Confirm": "¿Quiere aceptar este cliente?",
"Livechat_title": "Titulo de LiveChat",
"Livechat_title_color": "Color de fondo del titulo de LiveChat",
"Livechat_transcript_already_requested_warning": "La transcripción de este chat ya ha sido solicitada y será enviada tan pronto como la conversación termine.",
"Livechat_transcript_has_been_requested": "Se ha solicitado la transcripción del chat.",
"Livechat_transcript_request_has_been_canceled": "Se canceló la solicitud de transcripción del chat.",
"Livechat_transcript_sent": "Se ha enviado una transcripción de LiveChat",
"Livechat_transcript_sent": "Se ha enviado una transcripción de Omnichannel",
"Livechat_transfer_return_to_the_queue": "__from__ devolvió el chat a la cola",
"Livechat_transfer_to_agent": "__from__ transfirió el chat a __to__",
"Livechat_transfer_to_agent_with_a_comment": "__from__ transfirió el chat a __to__ con un comentario: __comment__",
@ -2921,7 +2921,7 @@
"MongoDB_Deprecated": "MongoDB obsoleta",
"MongoDB_version_s_is_deprecated_please_upgrade_your_installation": "La versión de MongoDB %s está obsoleta, por favor actualice su instalación.",
"Monitor_added": "Monitoreo añadido",
"Monitor_history_for_changes_on": "Monitoriza el historial de cambios en",
"Monitor_history_for_changes_on": "Monitorea el historial de cambios en",
"Monitor_removed": "Monitor eliminado",
"Monitors": "Monitoreos",
"Monthly_Active_Users": "Usuarios activos mensuales",
@ -3107,10 +3107,10 @@
"Old Colors": "Colores Antiguos",
"Old Colors (minor)": "Colores Antiguos (menores)",
"Older_than": "Más antiguo que",
"Omnichannel": "LiveChat",
"Omnichannel_Directory": "Directorio de LiveChat",
"Omnichannel": "Omnichannel",
"Omnichannel_Directory": "Directorio de Omnichannel",
"Omnichannel_appearance": "Apariencia de LiveChat",
"Omnichannel_Contact_Center": "Centro de contactos de LiveChat",
"Omnichannel_Contact_Center": "Centro de contactos de Omnichannel",
"Omnichannel_contact_manager_routing": "Asignar nuevas conversaciones al administrador de contactos",
"Omnichannel_contact_manager_routing_Description": "Esta configuración asigna un chat al Administrador de contactos asignado, siempre que el Administrador de contactos esté en línea cuando se inicia el chat",
"Omnichannel_External_Frame": "Marco externo",
@ -3136,7 +3136,7 @@
"Open_conversations": "Conversaciones abiertas",
"Open_Days": "Días abiertos",
"Open_days_of_the_week": "Días abiertos de la semana",
"Open_Livechats": "LiveChats abiertos",
"Open_Livechats": "Salas Omnichannel abiertas",
"Open_thread": "Hilo abierto",
"Open_your_authentication_app_and_enter_the_code": "Abra su aplicación de autenticación e ingrese el código. También puede usar uno de sus códigos de respaldo.",
"Opened": "Abierto",
@ -3236,7 +3236,7 @@
"Please_fill_all_the_information": "Por favor complete toda la información",
"Please_fill_an_email": "Por favor introduzca un correo electrónico",
"Please_fill_name_and_email": "Por favor introduzca nombre y correo electrónico",
"Please_go_to_the_Administration_page_then_Livechat_Facebook": "Vaya a la página Administración y a continuació LiveChat> Facebook",
"Please_go_to_the_Administration_page_then_Livechat_Facebook": "Vaya a la página Administración y a continuación Omnichannel > Facebook",
"Please_select_an_user": "Por favor seleccione un usuario",
"Please_select_enabled_yes_or_no": "Por favor elija una opción para Habilitar",
"Please_select_visibility": "Por favor, seleccione una vista",
@ -3397,10 +3397,10 @@
"Remove_from_team": "Elimina del equipo",
"Remove_last_admin": "Eliminando el último administrador",
"Remove_someone_from_room": "Eliminar a alguien de la sala",
"remove-closed-livechat-room": "Quitar Cerrado Livechat",
"remove-closed-livechat-rooms": "Eliminar Rooms cerradas de LiveChat",
"remove-closed-livechat-rooms_description": "Permiso para eliminar salas Livechat cerradas",
"remove-livechat-department": "Elimina los departamentos Livechat",
"remove-closed-livechat-room": "Quitar sala cerrada de Omnichannel",
"remove-closed-livechat-rooms": "Eliminar salas cerradas de Omnichannel",
"remove-closed-livechat-rooms_description": "Permiso para eliminar salas Omnichannel cerradas",
"remove-livechat-department": "Elimina los departamentos Omnichannel",
"remove-user": "Eliminar usuario",
"remove-user_description": "Permiso para eliminar un usuario de una sala",
"Removed": "Eliminado",
@ -3622,8 +3622,8 @@
"Save_to_enable_this_action": "Guarde para habilitar esta acción",
"Save_To_Webdav": "Guardar en WebDAV",
"Save_Your_Encryption_Password": "Guarde su contraseña de cifrado",
"save-others-livechat-room-info": "Guardar información de otras salas de LiveChat",
"save-others-livechat-room-info_description": "Permiso para guardar información de otras salas de LiveChat",
"save-others-livechat-room-info": "Guardar información de otras salas de Omnichannel",
"save-others-livechat-room-info_description": "Permiso para guardar información de otras salas de Omnichannel",
"Saved": "Guardado",
"Saving": "Guardando",
"Scan_QR_code": "Con una aplicación autenticadora como Google Authenticator, Authy o Duo, escanee el código QR. Mostrará un código de 6 dígitos que debe ingresar a continuación.",
@ -3705,8 +3705,8 @@
"Send_your_JSON_payloads_to_this_URL": "Envíe sus payloads JSON a esta dirección URL.",
"send-many-messages": "Envíar muchos mensajes",
"send-many-messages_description": "Permiso para omitir el límite de velocidad de 5 mensajes por segundo",
"send-omnichannel-chat-transcript": "Enviar transcripción de la conversación LiveChat",
"send-omnichannel-chat-transcript_description": "Permiso para enviar transcripciones de conversaciones Livechat",
"send-omnichannel-chat-transcript": "Enviar transcripción de la conversación",
"send-omnichannel-chat-transcript_description": "Permiso para enviar transcripciones de conversaciones Omnichannel",
"Sender_Info": "Información del remitente",
"Sending": "Enviando...",
"Sent_an_attachment": "Enviado un archivo adjunto",
@ -3814,7 +3814,7 @@
"Smarsh_MissingEmail_Email_Description": "El correo electrónico para mostrar una cuenta de usuario cuando falta su dirección de correo electrónico, generalmente ocurre con las cuentas bot.",
"Smarsh_Timezone": "Zona horaria Smarsh",
"Smileys_and_People": "Sonrisas y Personas",
"SMS_Default_Omnichannel_Department": "Departamento de LiveChat (por defecto)",
"SMS_Default_Omnichannel_Department": "Departamento de Omnichannel (por defecto)",
"SMS_Default_Omnichannel_Department_Description": "Si se establece, todos los nuevos chats entrantes iniciados por esta integración se enrutarán a este departamento.",
"SMS_Enabled": "SMS Habilitado",
"SMTP": "SMTP",
@ -3878,11 +3878,11 @@
"Stats_Total_Installed_Apps": "Total de aplicaciones instaladas",
"Stats_Total_Integrations": "Total de integraciones",
"Stats_Total_Integrations_With_Script_Enabled": "Integraciones totales con script habilitado",
"Stats_Total_Livechat_Rooms": "Total de salas de LiveChat",
"Stats_Total_Livechat_Rooms": "Total de salas de Omnichannel",
"Stats_Total_Messages": "Total de Mensajes",
"Stats_Total_Messages_Channel": "Total de mensajes en canales",
"Stats_Total_Messages_Direct": "Total de mensajes en mensajes directos",
"Stats_Total_Messages_Livechat": "Total de mensajes en LiveChat",
"Stats_Total_Messages_Livechat": "Total de mensajes en Omnichannel",
"Stats_Total_Messages_PrivateGroup": "Total de mensajes en grupos privados",
"Stats_Total_Outgoing_Integrations": "Total de integraciones salientes",
"Stats_Total_Private_Groups": "Total de grupos privados",
@ -4158,8 +4158,8 @@
"Transcript_message": "Mensaje para mostrar al preguntar sobre la transcripción",
"Transcript_of_your_livechat_conversation": "Transcripción de su conversación de LiveChat.",
"Transcript_Request": "Solicitud de transcripción",
"transfer-livechat-guest": "Transferir invitados de Livechat",
"transfer-livechat-guest_description": "Permiso para transferir invitados al Livechat",
"transfer-livechat-guest": "Transferir invitados de Omnichannel",
"transfer-livechat-guest_description": "Permiso para transferir invitados al Omnichannel",
"Transferred": "Transferido",
"Translate": "Traducir",
"Translated": "Traducido",
@ -4452,32 +4452,32 @@
"view-joined-room": "Ver sala unida",
"view-joined-room_description": "Permiso para ver los canales actualmente unidos",
"view-l-room": "Ver salas de LiveChat",
"view-l-room_description": "Permiso para ver salas de LiveChat",
"view-livechat-analytics": "Ver analíticas de LiveChat",
"view-livechat-analytics_description": "Permiso para ver análisis de Livechat",
"view-l-room_description": "Permiso para ver salas de Omnichannel",
"view-livechat-analytics": "Ver analíticas de Omnichannel",
"view-livechat-analytics_description": "Permiso para ver análisis de Omnichannel",
"view-livechat-appearance": "Ver apariencia Livechat",
"view-livechat-appearance_description": "Permiso para ver la apariencia de Livechat",
"view-livechat-business-hours": "Ver horario comercial Livechat",
"view-livechat-business-hours_description": "Permiso para ver el horario comercial de Livechat",
"view-livechat-current-chats": "Ver chats actuales Livechat",
"view-livechat-current-chats_description": "Permiso para ver los chats actuales de Livechat",
"view-livechat-departments": "Ver departamentos de LiveChat",
"view-livechat-manager": "Ver el administrador de LiveChat",
"view-livechat-manager_description": "Permiso para ver otros administradores de LiveChat",
"view-livechat-monitor": "Ver los monitores de Livechat",
"view-livechat-queue": "Ver las colas de LiveChat",
"view-livechat-room-closed-by-another-agent": "Ver las Rooms de LiveChat cerradas por otro agente",
"view-livechat-room-closed-same-department": "Ver las salas de LiveChat cerradas por otro agente del mismo departamento",
"view-livechat-room-closed-same-department_description": "Permiso para ver salas de Livechat cerradas por otro agente del mismo departamento",
"view-livechat-room-customfields": "Ver campos personalizados de Room Livechat",
"view-livechat-room-customfields_description": "Permiso para ver los campos personalizados de la sala de Livechat",
"view-livechat-rooms": "Ver las salas de LiveChat",
"view-livechat-rooms_description": "Permiso para ver otros canales de LiveChat",
"view-livechat-triggers": "Ver activadores Livechat",
"view-livechat-triggers_description": "Permiso para ver los activadores de Livechat",
"view-livechat-webhooks": "Ver webhooks Livechat",
"view-livechat-webhooks_description": "Permiso para ver webhooks Livechat",
"view-livechat-unit": "Ver las unidades de Livechat",
"view-livechat-business-hours": "Ver horario comercial Omnichannel",
"view-livechat-business-hours_description": "Permiso para ver el horario comercial de Omnichannel",
"view-livechat-current-chats": "Ver chats actuales Omnichannel",
"view-livechat-current-chats_description": "Permiso para ver los chats actuales de Omnichannel",
"view-livechat-departments": "Ver departamentos de Omnichannel",
"view-livechat-manager": "Ver el administrador de Omnichannel",
"view-livechat-manager_description": "Permiso para ver otros administradores de Omnichannel",
"view-livechat-monitor": "Ver los monitores de Omnichannel",
"view-livechat-queue": "Ver las colas de Omnichannel",
"view-livechat-room-closed-by-another-agent": "Ver las salas de Omnichannel cerradas por otro agente",
"view-livechat-room-closed-same-department": "Ver las salas de Omnichannel cerradas por otro agente del mismo departamento",
"view-livechat-room-closed-same-department_description": "Permiso para ver salas de Omnichannel cerradas por otro agente del mismo departamento",
"view-livechat-room-customfields": "Ver campos personalizados de salas Omnichannel",
"view-livechat-room-customfields_description": "Permiso para ver los campos personalizados de la sala de Omnichannel",
"view-livechat-rooms": "Ver las salas de Omnichannel",
"view-livechat-rooms_description": "Permiso para ver otros canales de Omnichannel",
"view-livechat-triggers": "Ver activadores Omnichannel",
"view-livechat-triggers_description": "Permiso para ver los activadores de Omnichannel",
"view-livechat-webhooks": "Ver webhooks Omnichannel",
"view-livechat-webhooks_description": "Permiso para ver webhooks Omnichannel",
"view-livechat-unit": "Ver las unidades de Omnichannel",
"view-logs": "Ver los registros",
"view-logs_description": "Permiso para ver los registros del servidor",
"view-other-user-channels": "Ver otros canales de usuario",
@ -4584,8 +4584,8 @@
"You_can_close_this_window_now": "Ya puedes cerrar esta ventana.",
"You_can_search_using_RegExp_eg": "Puede buscar utilizando una <a href=\"https://en.wikipedia.org/wiki/Regular_expression\" target=\"_blank\">Expresión Regular</a>. Por ejemplo: <code class=\"code-colors inline\">/^text$/i</code>",
"You_can_use_an_emoji_as_avatar": "También puede utilizar un emoji como avatar.",
"You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Se puede utilizar WebHooks para integrar fácilmente LiveChat con su CRM.",
"You_cant_leave_a_livechat_room_Please_use_the_close_button": "No puedes salir de una sala LiveChat. Por favor, use el botón cerrar.",
"You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Se puede utilizar WebHooks para integrar fácilmente Omnichannel con su CRM.",
"You_cant_leave_a_livechat_room_Please_use_the_close_button": "No puedes salir de una sala Omnichannel. Por favor, use el botón cerrar.",
"You_followed_this_message": "Seguiste este mensaje.",
"You_have_a_new_message": "Tiene un nuevo mensaje",
"You_have_been_muted": "Has sido silenciado y no puedes hablar en esta sala",

@ -24,7 +24,13 @@ export class Logger {
}
section(name: string): P.Logger {
return this.logger.child({ section: name });
const child = this.logger.child({ section: name });
logLevel.on('changed', (level) => {
child.level = getLevel(level);
});
return child;
}
level(newLevel: string): void {

@ -9,7 +9,7 @@ import { settings } from '../../app/settings/server';
import { Info, getMongoInfo } from '../../app/utils/server';
import { Users } from '../../app/models/server';
import { sendMessagesToAdmins } from '../lib/sendMessagesToAdmins';
import { showErrorBox, showSuccessBox, showWarningBox } from '../lib/logger/showBox';
import { showErrorBox, showWarningBox, showSuccessBox } from '../lib/logger/showBox';
const exitIfNotBypassed = (ignore, errorCode = 1) => {
if (typeof ignore === 'string' && ['yes', 'true'].includes(ignore.toLowerCase())) {
@ -61,8 +61,8 @@ Meteor.startup(function() {
exitIfNotBypassed(process.env.BYPASS_NODEJS_VALIDATION);
}
if (!semver.satisfies(semver.coerce(mongoVersion), '>=3.4.0')) {
msg += ['', '', 'YOUR CURRENT MONGODB VERSION IS NOT SUPPORTED,', 'PLEASE UPGRADE TO VERSION 3.4 OR LATER'].join('\n');
if (!semver.satisfies(semver.coerce(mongoVersion), '>=3.6.0')) {
msg += ['', '', 'YOUR CURRENT MONGODB VERSION IS NOT SUPPORTED,', 'PLEASE UPGRADE TO VERSION 3.6 OR LATER'].join('\n');
showErrorBox('SERVER ERROR', msg);
exitIfNotBypassed(process.env.BYPASS_MONGO_VALIDATION);
@ -71,8 +71,8 @@ Meteor.startup(function() {
showSuccessBox('SERVER RUNNING', msg);
// Deprecation
if (!semver.satisfies(semver.coerce(mongoVersion), '>=3.6.0')) {
msg = [`YOUR CURRENT MONGODB VERSION (${ mongoVersion }) IS DEPRECATED.`, 'IT WILL NOT BE SUPPORTED ON ROCKET.CHAT VERSION 4.0.0 AND GREATER,', 'PLEASE UPGRADE MONGODB TO VERSION 3.6 OR GREATER'].join('\n');
if (!semver.satisfies(semver.coerce(mongoVersion), '>=4.2.0')) {
msg = [`YOUR CURRENT MONGODB VERSION (${ mongoVersion }) IS DEPRECATED.`, 'IT WILL NOT BE SUPPORTED ON ROCKET.CHAT VERSION 5.0.0 AND GREATER,', 'PLEASE UPGRADE MONGODB TO VERSION 4.2 OR GREATER'].join('\n');
showWarningBox('DEPRECATION', msg);
const id = `mongodbDeprecation_${ mongoVersion.replace(/[^0-9]/g, '_') }`;

Loading…
Cancel
Save