feat: ldap custom variables / string manipulation (#35717)
parent
b718fe7d05
commit
2c190740d0
@ -0,0 +1,7 @@ |
||||
--- |
||||
'@rocket.chat/core-typings': minor |
||||
'@rocket.chat/i18n': minor |
||||
'@rocket.chat/meteor': minor |
||||
--- |
||||
|
||||
Adds new settings to allow configuring custom variables with string manipulation functions on the LDAP data mapper |
||||
@ -0,0 +1,65 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
import { expect } from 'chai'; |
||||
import 'mocha'; |
||||
|
||||
import { getLdapDynamicValue } from './getLdapDynamicValue'; |
||||
|
||||
describe('getLdapDynamicValue', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
displayName: 'John Doe', |
||||
email: 'john.doe@example.com', |
||||
uid: 'johndoe', |
||||
emptyField: '', |
||||
}; |
||||
|
||||
it('should return undefined if attributeSetting is undefined', () => { |
||||
const result = getLdapDynamicValue(ldapUser, undefined); |
||||
expect(result).to.be.undefined; |
||||
}); |
||||
|
||||
it('should return the correct value from a single valid attribute', () => { |
||||
const result = getLdapDynamicValue(ldapUser, 'displayName'); |
||||
expect(result).to.equal('John Doe'); |
||||
}); |
||||
|
||||
it('should return the correct value from a template attribute', () => { |
||||
const result = getLdapDynamicValue(ldapUser, 'Hello, #{displayName}!'); |
||||
expect(result).to.equal('Hello, John Doe!'); |
||||
}); |
||||
|
||||
it('should replace missing keys with an empty string in a template', () => { |
||||
const result = getLdapDynamicValue(ldapUser, 'Hello, #{nonExistentField}!'); |
||||
expect(result).to.equal('Hello, !'); |
||||
}); |
||||
|
||||
it('should return the first valid key from a CSV list of attributes', () => { |
||||
const result = getLdapDynamicValue(ldapUser, 'nonExistentField,email,uid'); |
||||
expect(result).to.equal('john.doe@example.com'); |
||||
}); |
||||
|
||||
it('should return undefined if none of the keys in CSV list exist', () => { |
||||
const result = getLdapDynamicValue(ldapUser, 'nonExistentField,anotherNonExistentField'); |
||||
expect(result).to.be.undefined; |
||||
}); |
||||
|
||||
it('should handle attribute keys with surrounding whitespace correctly', () => { |
||||
const result = getLdapDynamicValue(ldapUser, ' email '); |
||||
expect(result).to.equal('john.doe@example.com'); |
||||
}); |
||||
|
||||
it('should correctly resolve multiple variables in a template', () => { |
||||
const result = getLdapDynamicValue(ldapUser, 'User: #{displayName}, Email: #{email}, UID: #{uid}'); |
||||
expect(result).to.equal('User: John Doe, Email: john.doe@example.com, UID: johndoe'); |
||||
}); |
||||
|
||||
it('should return undefined if the attribute has an empty value', () => { |
||||
const result = getLdapDynamicValue(ldapUser, 'emptyField'); |
||||
expect(result).to.be.undefined; |
||||
}); |
||||
|
||||
it('should return an empty string if using only a template attribute that has an empty value', () => { |
||||
const result = getLdapDynamicValue(ldapUser, '#{emptyField}'); |
||||
expect(result).to.be.equal(''); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,31 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
|
||||
import { getLdapString } from './getLdapString'; |
||||
import { ldapKeyExists } from './ldapKeyExists'; |
||||
|
||||
export function getLdapDynamicValue(ldapUser: ILDAPEntry, attributeSetting: string | undefined): string | undefined { |
||||
if (!attributeSetting) { |
||||
return; |
||||
} |
||||
|
||||
// If the attribute setting is a template, then convert the variables in it
|
||||
if (attributeSetting.includes('#{')) { |
||||
return attributeSetting.replace(/#{(.+?)}/g, (_match, field) => { |
||||
const key = field.trim(); |
||||
|
||||
if (ldapKeyExists(ldapUser, key)) { |
||||
// We've already validated so it won't ever return undefined, but add a fallback to ensure it doesn't break if something gets changed
|
||||
return getLdapString(ldapUser, key) || ''; |
||||
} |
||||
|
||||
return ''; |
||||
}); |
||||
} |
||||
|
||||
// If it's not a template, then treat the setting as a CSV list of possible attribute names and return the first valid one.
|
||||
const attributeList: string[] = attributeSetting.replace(/\s/g, '').split(','); |
||||
const key = attributeList.find((field) => ldapKeyExists(ldapUser, field)); |
||||
if (key) { |
||||
return getLdapString(ldapUser, key); |
||||
} |
||||
} |
||||
@ -0,0 +1,47 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
import { expect } from 'chai'; |
||||
import 'mocha'; |
||||
|
||||
import { getLdapString } from './getLdapString'; |
||||
|
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
username: 'john_doe', |
||||
email: 'john.doe@example.com', |
||||
phoneNumber: '123-456-7890', |
||||
memberOf: 'group1,group2', |
||||
}; |
||||
|
||||
describe('getLdapString', () => { |
||||
it('should return the correct value for a given key', () => { |
||||
expect(getLdapString(ldapUser, 'username')).to.equal('john_doe'); |
||||
expect(getLdapString(ldapUser, 'email')).to.equal('john.doe@example.com'); |
||||
expect(getLdapString(ldapUser, 'phoneNumber')).to.equal('123-456-7890'); |
||||
expect(getLdapString(ldapUser, 'memberOf')).to.equal('group1,group2'); |
||||
}); |
||||
|
||||
it('should trim the key and return the correct value', () => { |
||||
expect(getLdapString(ldapUser, ' username ')).to.equal('john_doe'); |
||||
expect(getLdapString(ldapUser, ' email ')).to.equal('john.doe@example.com'); |
||||
}); |
||||
|
||||
it('should return undefined for non-existing keys', () => { |
||||
expect(getLdapString(ldapUser, 'nonExistingKey')).to.be.undefined; |
||||
expect(getLdapString(ldapUser, 'foo')).to.be.undefined; |
||||
}); |
||||
|
||||
it('should handle empty keys and return an empty string', () => { |
||||
expect(getLdapString(ldapUser, '')).to.be.undefined; |
||||
expect(getLdapString(ldapUser, ' ')).to.be.undefined; |
||||
}); |
||||
|
||||
it('should handle keys with only whitespace', () => { |
||||
expect(getLdapString(ldapUser, ' ')).to.be.undefined; |
||||
expect(getLdapString(ldapUser, ' ')).to.be.undefined; |
||||
}); |
||||
|
||||
it('should handle case-sensitive keys accurately', () => { |
||||
expect(getLdapString(ldapUser, 'Username')).to.be.undefined; |
||||
expect(getLdapString(ldapUser, 'EMAIL')).to.be.undefined; |
||||
}); |
||||
}); |
||||
@ -0,0 +1,5 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
|
||||
export function getLdapString(ldapUser: ILDAPEntry, key: string): string | undefined { |
||||
return ldapUser[key.trim()]; |
||||
} |
||||
@ -0,0 +1,94 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
import { expect } from 'chai'; |
||||
import { describe, it } from 'mocha'; |
||||
|
||||
import { ldapKeyExists } from './ldapKeyExists'; |
||||
|
||||
describe('ldapKeyExists', () => { |
||||
it('should return true when key exists and is not empty', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
cn: 'John Doe', |
||||
mail: 'john.doe@example.com', |
||||
}; |
||||
|
||||
expect(ldapKeyExists(ldapUser, 'cn')).to.be.true; |
||||
expect(ldapKeyExists(ldapUser, 'mail')).to.be.true; |
||||
}); |
||||
|
||||
it('should return false when key exists but is empty', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
cn: '', |
||||
mail: '', |
||||
}; |
||||
|
||||
expect(ldapKeyExists(ldapUser, 'cn')).to.be.false; |
||||
expect(ldapKeyExists(ldapUser, 'mail')).to.be.false; |
||||
}); |
||||
|
||||
it('should return false when key does not exist', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
cn: 'John Doe', |
||||
}; |
||||
expect(ldapKeyExists(ldapUser, 'mail')).to.be.false; |
||||
}); |
||||
|
||||
it('should trim the key before checking', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
cn: 'John Doe', |
||||
}; |
||||
expect(ldapKeyExists(ldapUser, ' cn ')).to.be.true; |
||||
expect(ldapKeyExists(ldapUser, ' mail ')).to.be.false; |
||||
}); |
||||
|
||||
it('should return false for empty keys', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
cn: 'John Doe', |
||||
}; |
||||
expect(ldapKeyExists(ldapUser, '')).to.be.false; |
||||
expect(ldapKeyExists(ldapUser, ' ')).to.be.false; |
||||
}); |
||||
|
||||
it('should handle keys with different casing', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
CN: 'John Doe', |
||||
}; |
||||
|
||||
expect(ldapKeyExists(ldapUser, 'CN')).to.be.true; |
||||
expect(ldapKeyExists(ldapUser, 'cn')).to.be.false; |
||||
}); |
||||
|
||||
// #TODO: We only work with strings so this doesn't matter, but why are numbers and booleans being considered "empty"?
|
||||
it('should treat primitive non-string values as empty', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
numberValue: 123, |
||||
booleanValue: true, |
||||
anotherBooleanValue: false, |
||||
}; |
||||
|
||||
expect(ldapKeyExists(ldapUser, 'numberValue')).to.be.false; |
||||
expect(ldapKeyExists(ldapUser, 'booleanValue')).to.be.false; |
||||
expect(ldapKeyExists(ldapUser, 'anotherBooleanValue')).to.be.false; |
||||
}); |
||||
|
||||
it('should treat non-string values as empty', () => { |
||||
const ldapUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
nullValue: null, |
||||
undefinedValue: undefined, |
||||
objectValue: {}, |
||||
arrayValue: [], |
||||
}; |
||||
|
||||
expect(ldapKeyExists(ldapUser, 'nullValue')).to.be.false; |
||||
expect(ldapKeyExists(ldapUser, 'undefinedValue')).to.be.false; |
||||
expect(ldapKeyExists(ldapUser, 'objectValue')).to.be.false; |
||||
expect(ldapKeyExists(ldapUser, 'arrayValue')).to.be.false; |
||||
}); |
||||
}); |
||||
@ -0,0 +1,6 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
import _ from 'underscore'; |
||||
|
||||
export function ldapKeyExists(ldapUser: ILDAPEntry, key: string): boolean { |
||||
return !_.isEmpty(ldapUser[key.trim()]); |
||||
} |
||||
@ -0,0 +1,31 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
|
||||
import { executeFallback, type LDAPVariableFallback } from './fallback'; |
||||
import { executeMatch, type LDAPVariableMatch } from './match'; |
||||
import { executeReplace, type LDAPVariableReplace } from './replace'; |
||||
import { executeSplit, type LDAPVariableSplit } from './split'; |
||||
import { executeSubstring, type LDAPVariableSubString } from './substring'; |
||||
|
||||
export type LDAPVariableOperation = |
||||
| LDAPVariableReplace |
||||
| LDAPVariableMatch |
||||
| LDAPVariableSubString |
||||
| LDAPVariableFallback |
||||
| LDAPVariableSplit; |
||||
|
||||
export function executeOperation(ldapUser: ILDAPEntry, input: string, operation?: LDAPVariableOperation): string | undefined { |
||||
switch (operation?.operation) { |
||||
case 'replace': |
||||
return executeReplace(input, operation); |
||||
case 'match': |
||||
return executeMatch(input, operation); |
||||
case 'substring': |
||||
return executeSubstring(input, operation); |
||||
case 'fallback': |
||||
return executeFallback(ldapUser, input, operation); |
||||
case 'split': |
||||
return executeSplit(input, operation); |
||||
} |
||||
|
||||
return input; |
||||
} |
||||
@ -0,0 +1,61 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
import { expect } from 'chai'; |
||||
import 'mocha'; |
||||
|
||||
import type { LDAPVariableFallback } from './fallback'; |
||||
import { executeFallback } from './fallback'; |
||||
|
||||
describe('executeFallback function', () => { |
||||
const mockUser: ILDAPEntry = { |
||||
_raw: {}, |
||||
defaultFallback: 'defaultFallbackValue', |
||||
}; |
||||
|
||||
it('should return the input value when it is valid and no minLength is provided', () => { |
||||
const input = 'validInput'; |
||||
const operation: LDAPVariableFallback = { operation: 'fallback', fallback: 'defaultFallback' }; |
||||
const result = executeFallback(mockUser, input, operation); |
||||
expect(result).to.equal(input); |
||||
}); |
||||
|
||||
it('should return the input value when it is valid and meets minLength requirement', () => { |
||||
const input = 'validInput'; |
||||
const operation: LDAPVariableFallback = { operation: 'fallback', fallback: 'defaultFallback', minLength: 5 }; |
||||
const result = executeFallback(mockUser, input, operation); |
||||
expect(result).to.equal(input); |
||||
}); |
||||
|
||||
it('should return fallback when input is invalid', () => { |
||||
const input = ''; |
||||
const operation: LDAPVariableFallback = { operation: 'fallback', fallback: 'defaultFallback' }; |
||||
const result = executeFallback(mockUser, input, operation); |
||||
expect(result).to.equal('defaultFallbackValue'); |
||||
}); |
||||
|
||||
it('should return fallback when input is too short', () => { |
||||
const input = 'short'; |
||||
const operation: LDAPVariableFallback = { operation: 'fallback', fallback: 'defaultFallback', minLength: 10 }; |
||||
const result = executeFallback(mockUser, input, operation); |
||||
expect(result).to.equal('defaultFallbackValue'); |
||||
}); |
||||
|
||||
it('should return fallback when input is undefined', () => { |
||||
const operation: LDAPVariableFallback = { operation: 'fallback', fallback: 'defaultFallback' }; |
||||
const result = executeFallback(mockUser, undefined as any, operation); |
||||
expect(result).to.equal('defaultFallbackValue'); |
||||
}); |
||||
|
||||
it('should return fallback when input is an empty string and minLength is zero', () => { |
||||
const input = ''; |
||||
const operation: LDAPVariableFallback = { operation: 'fallback', fallback: 'defaultFallback', minLength: 0 }; |
||||
const result = executeFallback(mockUser, input, operation); |
||||
expect(result).to.equal('defaultFallbackValue'); |
||||
}); |
||||
|
||||
it('should return fallback when input is an empty string and minLength is undefined', () => { |
||||
const input = ''; |
||||
const operation: LDAPVariableFallback = { operation: 'fallback', fallback: 'defaultFallback', minLength: undefined }; |
||||
const result = executeFallback(mockUser, input, operation); |
||||
expect(result).to.equal('defaultFallbackValue'); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,24 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
|
||||
import { getLdapDynamicValue } from '../getLdapDynamicValue'; |
||||
|
||||
export type LDAPVariableFallback = { |
||||
operation: 'fallback'; |
||||
fallback: string; |
||||
|
||||
minLength?: number; |
||||
}; |
||||
|
||||
export function executeFallback(ldapUser: ILDAPEntry, input: string, operation: LDAPVariableFallback): string | undefined { |
||||
let valid = Boolean(input); |
||||
|
||||
if (valid && typeof operation.minLength === 'number') { |
||||
valid = input.length >= operation.minLength; |
||||
} |
||||
|
||||
if (valid) { |
||||
return input; |
||||
} |
||||
|
||||
return getLdapDynamicValue(ldapUser, operation.fallback); |
||||
} |
||||
@ -0,0 +1,106 @@ |
||||
import { expect } from 'chai'; |
||||
import 'mocha'; |
||||
|
||||
import type { LDAPVariableMatch } from './match'; |
||||
import { executeMatch } from './match'; |
||||
|
||||
describe('executeMatch function', () => { |
||||
describe('Validation', () => { |
||||
it('throws an error when pattern is missing', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: '', |
||||
regex: true, |
||||
}; |
||||
expect(() => executeMatch('input', operation)).to.throw('Invalid MATCH operation.'); |
||||
}); |
||||
|
||||
it('throws an error when neither valueIfTrue nor indexToUse is provided', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: 'pattern', |
||||
}; |
||||
expect(() => executeMatch('input', operation)).to.throw('Invalid MATCH operation.'); |
||||
}); |
||||
}); |
||||
|
||||
describe('Non-Regex Matching', () => { |
||||
it('returns valueIfTrue when input matches pattern', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: 'hello', |
||||
valueIfTrue: 'matched', |
||||
valueIfFalse: 'not matched', |
||||
}; |
||||
expect(executeMatch('hello', operation)).to.be.equal('matched'); |
||||
}); |
||||
|
||||
it('returns valueIfFalse when input does not match pattern', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: 'hello', |
||||
valueIfTrue: 'matched', |
||||
valueIfFalse: 'not matched', |
||||
}; |
||||
expect(executeMatch('world', operation)).to.be.equal('not matched'); |
||||
}); |
||||
}); |
||||
|
||||
describe('Regex Matching', () => { |
||||
it('returns valueIfTrue when input matches regex pattern', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: '^hello$', |
||||
regex: true, |
||||
valueIfTrue: 'matched', |
||||
valueIfFalse: 'not matched', |
||||
}; |
||||
expect(executeMatch('hello', operation)).to.be.equal('matched'); |
||||
}); |
||||
|
||||
it('returns valueIfFalse when input does not match regex pattern', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: '^hello$', |
||||
regex: true, |
||||
valueIfTrue: 'matched', |
||||
valueIfFalse: 'not matched', |
||||
}; |
||||
expect(executeMatch('world', operation)).to.be.equal('not matched'); |
||||
}); |
||||
|
||||
it('uses flags when provided', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: '^HELLO$', |
||||
regex: true, |
||||
flags: 'i', |
||||
valueIfTrue: 'matched', |
||||
valueIfFalse: 'not matched', |
||||
}; |
||||
expect(executeMatch('hello', operation)).to.be.equal('matched'); |
||||
}); |
||||
}); |
||||
|
||||
describe('IndexToUse', () => { |
||||
it('returns the matched group at indexToUse', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: '(hello) (world)', |
||||
regex: true, |
||||
indexToUse: 1, |
||||
}; |
||||
expect(executeMatch('hello world', operation)).to.be.equal('hello'); |
||||
}); |
||||
|
||||
it('returns undefined when indexToUse is out of range', () => { |
||||
const operation: LDAPVariableMatch = { |
||||
operation: 'match', |
||||
pattern: '(hello)', |
||||
regex: true, |
||||
indexToUse: 2, |
||||
}; |
||||
expect(executeMatch('hello', operation)).to.be.undefined; |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,28 @@ |
||||
export type LDAPVariableMatch = { |
||||
operation: 'match'; |
||||
pattern: string; |
||||
regex?: boolean; |
||||
flags?: string; |
||||
indexToUse?: number; |
||||
valueIfTrue?: string; |
||||
valueIfFalse?: string; |
||||
}; |
||||
|
||||
export function executeMatch(input: string, operation: LDAPVariableMatch): string | undefined { |
||||
if (!operation.pattern || (typeof operation.valueIfTrue !== 'string' && typeof operation.indexToUse !== 'number')) { |
||||
throw new Error('Invalid MATCH operation.'); |
||||
} |
||||
|
||||
const pattern = operation.regex ? new RegExp(operation.pattern, operation.flags) : operation.pattern; |
||||
|
||||
const result = input.match(pattern); |
||||
if (!result) { |
||||
return operation.valueIfFalse; |
||||
} |
||||
|
||||
if (typeof operation.indexToUse === 'number' && result.length > operation.indexToUse) { |
||||
return result[operation.indexToUse]; |
||||
} |
||||
|
||||
return operation.valueIfTrue; |
||||
} |
||||
@ -0,0 +1,105 @@ |
||||
import { expect } from 'chai'; |
||||
import 'mocha'; |
||||
|
||||
import type { LDAPVariableReplace } from './replace'; |
||||
import { executeReplace } from './replace'; |
||||
|
||||
describe('executeReplace', () => { |
||||
describe('Validation', () => { |
||||
it('throws an error when pattern is missing', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: '', |
||||
replacement: 'new-value', |
||||
}; |
||||
expect(() => executeReplace('input', operation)).to.throw('Invalid REPLACE operation.'); |
||||
}); |
||||
|
||||
it('throws an error when replacement is not a string', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: 'old-value', |
||||
replacement: 123 as any, |
||||
}; |
||||
expect(() => executeReplace('input', operation)).to.throw('Invalid REPLACE operation.'); |
||||
}); |
||||
}); |
||||
|
||||
describe('String Replacement', () => { |
||||
it('replaces the first occurrence of the pattern', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: 'old', |
||||
replacement: 'new', |
||||
}; |
||||
expect(executeReplace('old-value old-value', operation)).to.be.equal('new-value old-value'); |
||||
}); |
||||
|
||||
it('replaces all occurrences of the pattern when `all` is true', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: 'old', |
||||
replacement: 'new', |
||||
all: true, |
||||
}; |
||||
expect(executeReplace('old-value old-value', operation)).to.be.equal('new-value new-value'); |
||||
}); |
||||
}); |
||||
|
||||
describe('Regex Replacement', () => { |
||||
it('replaces the first occurrence of the pattern', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: 'old', |
||||
replacement: 'new', |
||||
regex: true, |
||||
}; |
||||
expect(executeReplace('old-value old-value', operation)).to.be.equal('new-value old-value'); |
||||
}); |
||||
|
||||
it('replaces all occurrences of the pattern when `regex` and `all` are true', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: 'old', |
||||
replacement: 'new', |
||||
regex: true, |
||||
all: true, |
||||
}; |
||||
expect(executeReplace('old-value old-value', operation)).to.be.equal('new-value new-value'); |
||||
}); |
||||
|
||||
it('uses the provided flags', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: 'OLD', |
||||
replacement: 'new', |
||||
regex: true, |
||||
flags: 'i', |
||||
}; |
||||
expect(executeReplace('OLD-value old-value', operation)).to.be.equal('new-value old-value'); |
||||
}); |
||||
|
||||
it('adds the `g` flag when `all` is true', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: 'old', |
||||
replacement: 'new', |
||||
regex: true, |
||||
all: true, |
||||
}; |
||||
expect(executeReplace('old-value old-value', operation)).to.be.equal('new-value new-value'); |
||||
}); |
||||
|
||||
it('does not duplicate the `g` flag when already present', () => { |
||||
const operation: LDAPVariableReplace = { |
||||
operation: 'replace', |
||||
pattern: 'old', |
||||
replacement: 'new', |
||||
regex: true, |
||||
all: true, |
||||
flags: 'g', |
||||
}; |
||||
expect(executeReplace('old-value old-value', operation)).to.be.equal('new-value new-value'); |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,23 @@ |
||||
export type LDAPVariableReplace = { |
||||
operation: 'replace'; |
||||
pattern: string; |
||||
regex?: boolean; |
||||
flags?: string; |
||||
all?: boolean; |
||||
replacement: string; |
||||
}; |
||||
|
||||
export function executeReplace(input: string, operation: LDAPVariableReplace): string { |
||||
if (!operation.pattern || typeof operation.replacement !== 'string') { |
||||
throw new Error('Invalid REPLACE operation.'); |
||||
} |
||||
|
||||
const flags = operation.regex && operation.all ? `${operation.flags || ''}${operation.flags?.includes('g') ? '' : 'g'}` : operation.flags; |
||||
const pattern = operation.regex ? new RegExp(operation.pattern, flags) : operation.pattern; |
||||
|
||||
if (operation.all) { |
||||
return input.replaceAll(pattern, operation.replacement); |
||||
} |
||||
|
||||
return input.replace(pattern, operation.replacement); |
||||
} |
||||
@ -0,0 +1,48 @@ |
||||
import { expect } from 'chai'; |
||||
import 'mocha'; |
||||
|
||||
import type { LDAPVariableSplit } from './split'; |
||||
import { executeSplit } from './split'; |
||||
|
||||
describe('executeSplit function', () => { |
||||
it('should throw an error if the pattern is empty', () => { |
||||
const operation: LDAPVariableSplit = { operation: 'split', pattern: '' }; |
||||
expect(() => executeSplit('input', operation)).to.throw('Invalid SPLIT operation.'); |
||||
}); |
||||
|
||||
it('should split the input string by the pattern and return the first element by default', () => { |
||||
const operation: LDAPVariableSplit = { operation: 'split', pattern: ',' }; |
||||
const result = executeSplit('hello,world', operation); |
||||
expect(result).to.be.equal('hello'); |
||||
}); |
||||
|
||||
it('should split the input string by the pattern and return the element at the specified index', () => { |
||||
const operation: LDAPVariableSplit = { operation: 'split', pattern: ',', indexToUse: 1 }; |
||||
const result = executeSplit('hello,world', operation); |
||||
expect(result).to.be.equal('world'); |
||||
}); |
||||
|
||||
it('should return undefined if the index is out of range', () => { |
||||
const operation: LDAPVariableSplit = { operation: 'split', pattern: ',', indexToUse: 2 }; |
||||
const result = executeSplit('hello,world', operation); |
||||
expect(result).to.be.undefined; |
||||
}); |
||||
|
||||
it('should return undefined if the input string does not contain the pattern', () => { |
||||
const operation: LDAPVariableSplit = { operation: 'split', pattern: ',' }; |
||||
const result = executeSplit('helloworld', operation); |
||||
expect(result).to.be.equal('helloworld'); |
||||
}); |
||||
|
||||
it('should return the first element if the input string is empty', () => { |
||||
const operation: LDAPVariableSplit = { operation: 'split', pattern: ',' }; |
||||
const result = executeSplit('', operation); |
||||
expect(result).to.be.equal(''); |
||||
}); |
||||
|
||||
it('should return undefined if the input string is undefined', () => { |
||||
const operation: LDAPVariableSplit = { operation: 'split', pattern: ',' }; |
||||
const result = executeSplit(undefined as any, operation); |
||||
expect(result).to.be.undefined; |
||||
}); |
||||
}); |
||||
@ -0,0 +1,30 @@ |
||||
export type LDAPVariableSplit = { |
||||
operation: 'split'; |
||||
pattern: string; |
||||
indexToUse?: number; |
||||
}; |
||||
|
||||
export function executeSplit(input: string, operation: LDAPVariableSplit): string | undefined { |
||||
if (!operation.pattern) { |
||||
throw new Error('Invalid SPLIT operation.'); |
||||
} |
||||
|
||||
if (!input) { |
||||
return input; |
||||
} |
||||
|
||||
const result = input.split(operation.pattern); |
||||
if (!result) { |
||||
return; |
||||
} |
||||
|
||||
if (typeof operation.indexToUse === 'number') { |
||||
if (result.length > operation.indexToUse) { |
||||
return result[operation.indexToUse]; |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
return result.shift(); |
||||
} |
||||
@ -0,0 +1,48 @@ |
||||
import { expect } from 'chai'; |
||||
import 'mocha'; |
||||
|
||||
import type { LDAPVariableSubString } from './substring'; |
||||
import { executeSubstring } from './substring'; |
||||
|
||||
describe('executeSubstring function', () => { |
||||
it('should throw an error if the start is missing', () => { |
||||
const operation: LDAPVariableSubString = { operation: 'substring' } as unknown as LDAPVariableSubString; |
||||
expect(() => executeSubstring('input', operation)).to.throw('Invalid SUBSTRING operation.'); |
||||
}); |
||||
|
||||
it('should throw an error if the start is invalid', () => { |
||||
const operation: LDAPVariableSubString = { operation: 'substring', start: 0, end: null } as unknown as LDAPVariableSubString; |
||||
expect(() => executeSubstring('input', operation)).to.throw('Invalid SUBSTRING operation.'); |
||||
}); |
||||
|
||||
it('should get the substring of the input, using the start param', () => { |
||||
const result = executeSubstring('hello world', { operation: 'substring', start: 6 }); |
||||
expect(result).to.be.equal('world'); |
||||
}); |
||||
|
||||
it('should get the whole string when the start is zero', () => { |
||||
const result = executeSubstring('hello world', { operation: 'substring', start: 0 }); |
||||
expect(result).to.be.equal('hello world'); |
||||
}); |
||||
|
||||
it('should get the substring of the input, using the start and end param', () => { |
||||
const result = executeSubstring('hello world', { operation: 'substring', start: 6, end: 8 }); |
||||
expect(result).to.be.equal('wo'); |
||||
}); |
||||
|
||||
it('should work backwards if end is smaller than start', () => { |
||||
const result = executeSubstring('hello world', { operation: 'substring', start: 5, end: 0 }); |
||||
expect(result).to.be.equal('hello'); |
||||
}); |
||||
|
||||
it('should get an empty string if start and end are the same', () => { |
||||
expect(executeSubstring('hello world', { operation: 'substring', start: 0, end: 0 })).to.be.equal(''); |
||||
expect(executeSubstring('hello world', { operation: 'substring', start: 5, end: 5 })).to.be.equal(''); |
||||
}); |
||||
|
||||
it('should treat negative values as zero', () => { |
||||
expect(executeSubstring('hello world', { operation: 'substring', start: -4, end: 5 })).to.be.equal('hello'); |
||||
expect(executeSubstring('hello world', { operation: 'substring', start: 5, end: -4 })).to.be.equal('hello'); |
||||
expect(executeSubstring('hello world', { operation: 'substring', start: -5, end: -4 })).to.be.equal(''); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,13 @@ |
||||
export type LDAPVariableSubString = { |
||||
operation: 'substring'; |
||||
start: number; |
||||
end?: number; |
||||
}; |
||||
|
||||
export function executeSubstring(input: string, operation: LDAPVariableSubString): string | undefined { |
||||
if (typeof operation.start !== 'number' || (operation.end !== undefined && typeof operation.end !== 'number')) { |
||||
throw new Error('Invalid SUBSTRING operation.'); |
||||
} |
||||
|
||||
return input.substring(operation.start, operation.end); |
||||
} |
||||
@ -0,0 +1,38 @@ |
||||
import type { ILDAPEntry } from '@rocket.chat/core-typings'; |
||||
|
||||
import { mapLogger } from './Logger'; |
||||
import { getLdapDynamicValue } from './getLdapDynamicValue'; |
||||
import { executeOperation, type LDAPVariableOperation } from './operations/executeOperation'; |
||||
|
||||
export type LDAPVariableConfiguration = { |
||||
input: string; |
||||
output?: LDAPVariableOperation; |
||||
}; |
||||
export type LDAPVariableMap = Record<string, LDAPVariableConfiguration>; |
||||
|
||||
export function processLdapVariables(entry: ILDAPEntry, variableMap: LDAPVariableMap): ILDAPEntry { |
||||
if (!variableMap || !Object.keys(variableMap).length) { |
||||
mapLogger.debug('No LDAP variables to process.'); |
||||
return entry; |
||||
} |
||||
|
||||
for (const variableName in variableMap) { |
||||
if (!variableMap.hasOwnProperty(variableName)) { |
||||
continue; |
||||
} |
||||
|
||||
const variableData = variableMap[variableName]; |
||||
if (!variableData?.input) { |
||||
continue; |
||||
} |
||||
|
||||
const input = getLdapDynamicValue(entry, variableData.input) || ''; |
||||
const output = executeOperation(entry, input, variableData.output) || ''; |
||||
|
||||
mapLogger.debug({ msg: 'Processed LDAP variable.', variableName, input, output }); |
||||
|
||||
entry[variableName] = output; |
||||
} |
||||
|
||||
return entry; |
||||
} |
||||
Loading…
Reference in new issue