@open-xchange/appsuite-codeceptjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +47 -0
- package/README.md +40 -0
- package/chai.d.ts +5 -0
- package/customRerun.js +135 -0
- package/global.d.ts +5 -0
- package/index.js +187 -0
- package/package.json +39 -0
- package/src/actor.js +174 -0
- package/src/appsuiteHttpClient.js +155 -0
- package/src/chai.d.ts +6 -0
- package/src/chai.js +58 -0
- package/src/contexts/contexts.js +172 -0
- package/src/contexts/reseller.js +248 -0
- package/src/contexts.js +29 -0
- package/src/event.js +54 -0
- package/src/helper.js +817 -0
- package/src/pageobjects/calendar.js +226 -0
- package/src/pageobjects/contacts.js +148 -0
- package/src/pageobjects/drive.js +96 -0
- package/src/pageobjects/fragments/contact-autocomplete.js +45 -0
- package/src/pageobjects/fragments/contact-picker.js +50 -0
- package/src/pageobjects/fragments/dialogs.js +41 -0
- package/src/pageobjects/fragments/search.js +54 -0
- package/src/pageobjects/fragments/settings-mailfilter.js +90 -0
- package/src/pageobjects/fragments/settings.js +71 -0
- package/src/pageobjects/fragments/tinymce.js +41 -0
- package/src/pageobjects/fragments/topbar.js +43 -0
- package/src/pageobjects/fragments/viewer.js +67 -0
- package/src/pageobjects/mail.js +67 -0
- package/src/pageobjects/mobile/mobileCalendar.js +41 -0
- package/src/pageobjects/mobile/mobileContacts.js +40 -0
- package/src/pageobjects/mobile/mobileMail.js +51 -0
- package/src/pageobjects/tasks.js +58 -0
- package/src/plugins/emptyModule/index.js +21 -0
- package/src/plugins/settingsInit/index.js +35 -0
- package/src/plugins/testmetrics/index.js +135 -0
- package/src/soap/services/context.js +147 -0
- package/src/soap/services/oxaas.js +36 -0
- package/src/soap/services/resellerContext.js +65 -0
- package/src/soap/services/resellerUser.js +100 -0
- package/src/soap/services/user.js +114 -0
- package/src/soap/services/util.js +39 -0
- package/src/soap/soap.js +172 -0
- package/src/users/reseller.js +233 -0
- package/src/users/users.js +183 -0
- package/src/users.js +29 -0
- package/src/util.js +104 -0
- package/steps.d.ts +16 -0
package/src/soap/soap.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
*
|
|
5
|
+
* This code is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
17
|
+
*
|
|
18
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { performance, PerformanceObserver } = require('node:perf_hooks')
|
|
22
|
+
const SOAP = require('soap')
|
|
23
|
+
const dotenv = require('dotenv')
|
|
24
|
+
|
|
25
|
+
let AbortError
|
|
26
|
+
const pRetry = import('p-retry').then(module => {
|
|
27
|
+
AbortError = module.AbortError
|
|
28
|
+
return module.default
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
dotenv.config({ path: '.env' })
|
|
32
|
+
dotenv.config({ path: '.env.defaults' })
|
|
33
|
+
|
|
34
|
+
// This flag enables debug output including timing information.
|
|
35
|
+
const debug = process.env.DEBUG_SOAP === 'true'
|
|
36
|
+
|
|
37
|
+
// This URL is used to create the SOAP client.
|
|
38
|
+
const provisioningUrl = String(process.env.PROVISIONING_URL).replace(/\/$/, '')
|
|
39
|
+
|
|
40
|
+
// This user is used to authenticate against the provisioning API.
|
|
41
|
+
const provisioningAuth = {
|
|
42
|
+
auth: {
|
|
43
|
+
login: process.env.E2E_ADMIN_USER,
|
|
44
|
+
password: process.env.E2E_ADMIN_PW
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* This code creates a PerformanceObserver instance which asynchronously observes
|
|
50
|
+
* performance measurement events. It logs the name of the event (which is the name
|
|
51
|
+
* of the SOAP method in this context) and the time it took to execute in milliseconds.
|
|
52
|
+
*
|
|
53
|
+
* This is used for performance tracking of the SOAP methods.
|
|
54
|
+
* @see https://nodejs.org/api/perf_hooks.html
|
|
55
|
+
* @params {PerformanceObserverEntryList} items The list of performance entries.
|
|
56
|
+
* @returns {void}
|
|
57
|
+
*/
|
|
58
|
+
const obs = new PerformanceObserver((items) => {
|
|
59
|
+
items.getEntries().forEach((entry) => {
|
|
60
|
+
if (debug) console.log(`${entry.name} took ${Math.round(entry.duration)}ms`)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
obs.observe({ entryTypes: ['measure'] })
|
|
64
|
+
|
|
65
|
+
async function logSoapError (e) {
|
|
66
|
+
console.error(e?.originalError?.root?.Envelope?.Body?.Fault?.faultstring || e.message)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* This function checks if the specified error should abort the retry.
|
|
71
|
+
* @param {Error} error The error to check.
|
|
72
|
+
* @returns {boolean} True if the error should abort the retry, false otherwise.
|
|
73
|
+
**/
|
|
74
|
+
function shouldAbortRetry (error) {
|
|
75
|
+
try {
|
|
76
|
+
const fault = error.root.Envelope.Body.Fault
|
|
77
|
+
const details = fault.detail
|
|
78
|
+
const blockedFaultStrings = [
|
|
79
|
+
/Context \d+ already exists/,
|
|
80
|
+
/Authentication failed/,
|
|
81
|
+
/Context \d+ does not exist/,
|
|
82
|
+
/CloudPluginException: username .* already exists/,
|
|
83
|
+
/CloudPluginException: context name must begin/,
|
|
84
|
+
/The displayname is already used/,
|
|
85
|
+
/already exists in this context/,
|
|
86
|
+
/Shared Domain already exists/,
|
|
87
|
+
/Shared Domain already in use/,
|
|
88
|
+
/No such user/
|
|
89
|
+
]
|
|
90
|
+
const blockedExceptions = [
|
|
91
|
+
'ContextExistsException',
|
|
92
|
+
'InvalidCredentialsException',
|
|
93
|
+
'NoSuchContextException'
|
|
94
|
+
]
|
|
95
|
+
// return "details object contains any of the blocked items"
|
|
96
|
+
return blockedFaultStrings.some(msg => msg.test(fault.faultstring)) ||
|
|
97
|
+
blockedExceptions.some(exception => Object.hasOwnProperty.call(details, exception))
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// some other error which is never blocked
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* This function creates a SOAP client for the specified service type.
|
|
106
|
+
* @param {string} type The name of the service type.
|
|
107
|
+
* @returns {Promise<Object>} The SOAP client.
|
|
108
|
+
*/
|
|
109
|
+
async function createClientAsync (type) {
|
|
110
|
+
const startMark = `${type}-start`
|
|
111
|
+
const endMark = `${type}-end`
|
|
112
|
+
performance.mark(startMark)
|
|
113
|
+
const endpoint = `${provisioningUrl}/webservices/${type}`
|
|
114
|
+
const url = `${endpoint}/?wsdl`
|
|
115
|
+
const client = await SOAP.createClientAsync(url, {
|
|
116
|
+
endpoint,
|
|
117
|
+
suppressStack: true,
|
|
118
|
+
wsdl_options: {
|
|
119
|
+
forever: true
|
|
120
|
+
},
|
|
121
|
+
gzip: true
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// https://stackoverflow.com/questions/30740415/namespace-for-array-field-in-node-soap-client-node-js
|
|
125
|
+
client.wsdl.definitions.xmlns.ns1 = 'http://dataobjects.soap.admin.openexchange.com/xsd'
|
|
126
|
+
client.wsdl.xmlnsInEnvelope = client.wsdl._xmlnsMap()
|
|
127
|
+
performance.mark(endMark)
|
|
128
|
+
performance.measure(` ⏱ SOAP: ${type} -> createClientAsync`, startMark, endMark)
|
|
129
|
+
|
|
130
|
+
// This proxy effectively adds error handling, authentication and performance measurements to all methods of the SOAP client.
|
|
131
|
+
return new Proxy(client, {
|
|
132
|
+
get (target, prop, receiver) {
|
|
133
|
+
const origMethod = Reflect.get(target, prop, receiver)
|
|
134
|
+
if (typeof origMethod === 'function') {
|
|
135
|
+
return async function (options, clientOptions, ...args) {
|
|
136
|
+
const startMark = `${prop}-start`
|
|
137
|
+
const endMark = `${prop}-end`
|
|
138
|
+
performance.mark(startMark)
|
|
139
|
+
const soapOptions = { ...provisioningAuth, ...options }
|
|
140
|
+
if (soapOptions.auth) {
|
|
141
|
+
// only send login and password instead of complete admin object.
|
|
142
|
+
// this can fail because of ambiguous namespacing
|
|
143
|
+
soapOptions.auth = { login: soapOptions.auth.login, password: soapOptions.auth.password }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const result = await (await pRetry)(() => origMethod.apply(this, [soapOptions, { timeout: 10000, ...clientOptions }, ...args]), {
|
|
147
|
+
retries: 3,
|
|
148
|
+
onFailedAttempt: async error => {
|
|
149
|
+
if (shouldAbortRetry(error)) throw new (await AbortError)(error)
|
|
150
|
+
console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`)
|
|
151
|
+
}
|
|
152
|
+
}).catch(e => {
|
|
153
|
+
const soapError = e?.originalError?.root?.Envelope?.Body?.Fault
|
|
154
|
+
throw new Error(soapError?.faultstring || e.message)
|
|
155
|
+
})
|
|
156
|
+
performance.mark(endMark)
|
|
157
|
+
performance.measure(` ⏱ SOAP: ${type} -> ${prop}`, startMark, endMark)
|
|
158
|
+
// Return only the first result from the SOAP method call, as we don't need the SOAP envelope and other stuff.
|
|
159
|
+
if (!result || !result[0]) return
|
|
160
|
+
return result[0]?.return
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
return origMethod
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
createClientAsync,
|
|
171
|
+
logSoapError
|
|
172
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
*
|
|
5
|
+
* This code is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
17
|
+
*
|
|
18
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const event = require('../event')
|
|
22
|
+
|
|
23
|
+
const users = []
|
|
24
|
+
const usersToRemove = []
|
|
25
|
+
const preprovisionedUsers = []
|
|
26
|
+
const util = require('../util')
|
|
27
|
+
const short = require('short-uuid')
|
|
28
|
+
const resellerUserService = require('../soap/services/resellerUser')
|
|
29
|
+
const oxaasService = require('../soap/services/oxaas')
|
|
30
|
+
|
|
31
|
+
class User {
|
|
32
|
+
constructor (opt) {
|
|
33
|
+
this.userdata = opt.user
|
|
34
|
+
this.context = opt.context
|
|
35
|
+
this.session = {}
|
|
36
|
+
this.userAttributes = this.userdata.userAttributes || {}
|
|
37
|
+
return new Proxy(this, {
|
|
38
|
+
get (target, prop) {
|
|
39
|
+
return prop in target ? target[prop] : target.userdata[prop]
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async remove () {
|
|
45
|
+
await resellerUserService.remove(
|
|
46
|
+
this.context.id,
|
|
47
|
+
this.get('id')
|
|
48
|
+
).catch(async (err) => {
|
|
49
|
+
if (/No such user/.test(err.faultstring)) return // ignore
|
|
50
|
+
throw new util.PropagatedError(err)
|
|
51
|
+
})
|
|
52
|
+
users.splice(0, users.length, ...users.filter(u => u !== this))
|
|
53
|
+
event.emit(event.provisioning.user.removed, this)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async hasConfig (key, value) {
|
|
57
|
+
// Config structure is the following { userAttributes: { entries: [{ key: 'config', value: { entries: [{ key: 'key', value: value }] }}]} }
|
|
58
|
+
// Note that the soap lib will replace arrays with a single element by that element
|
|
59
|
+
|
|
60
|
+
// check if config exists and create config path if it does not exist
|
|
61
|
+
const userAttributes = this.userdata.userAttributes = this.userdata.userAttributes || {}
|
|
62
|
+
const entries = userAttributes.entries = userAttributes.entries ? [].concat(userAttributes.entries) : []
|
|
63
|
+
let configEntry = entries.find(entry => entry.key === 'config')
|
|
64
|
+
if (!configEntry) {
|
|
65
|
+
entries.push({ key: 'config', value: {} })
|
|
66
|
+
configEntry = entries[entries.length - 1]
|
|
67
|
+
}
|
|
68
|
+
const config = configEntry.value
|
|
69
|
+
const configEntries = config.entries = config.entries ? [].concat(config.entries) : []
|
|
70
|
+
let targetConfig = configEntries.find(entry => entry.key === key)
|
|
71
|
+
if (!targetConfig) {
|
|
72
|
+
configEntries.push({ key })
|
|
73
|
+
targetConfig = configEntries[configEntries.length - 1]
|
|
74
|
+
}
|
|
75
|
+
targetConfig.value = value
|
|
76
|
+
|
|
77
|
+
return resellerUserService.change(this.context, this.userdata)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
hasAccessCombination (accessCombinationName) {
|
|
81
|
+
return resellerUserService.changeByModuleAccessName(this.context, this.get('id'), accessCombinationName)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getModuleAccess () {
|
|
85
|
+
return resellerUserService.getModuleAccess(this.context, this.get('id'))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async hasModuleAccess (moduleAccess) {
|
|
89
|
+
const currentAccess = await this.getModuleAccess()
|
|
90
|
+
return resellerUserService.changeByModuleAccess(this.context, this.get('id'), currentAccess, moduleAccess)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
hasQuota (quota) {
|
|
94
|
+
return oxaasService.setMailQuota({
|
|
95
|
+
ctxid: this.context.id,
|
|
96
|
+
usrid: this.get('id'),
|
|
97
|
+
quota,
|
|
98
|
+
creds: util.admin()
|
|
99
|
+
}).catch((err) => { throw new util.PropagatedError(err) })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
hasAlias (alias) {
|
|
103
|
+
// See if this alias has already been declared
|
|
104
|
+
if (this.userdata.aliases.includes(alias)) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
// If not, add it and save
|
|
108
|
+
this.userdata.aliases.push(alias)
|
|
109
|
+
const userdata = { id: this.get('id'), aliases: this.userdata.aliases }
|
|
110
|
+
return resellerUserService.change(this.context, userdata)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
hasCapability (capability) {
|
|
114
|
+
this._setCapability(capability, true)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_setCapability (cap, value) {
|
|
118
|
+
const userAttributes = this.userAttributes
|
|
119
|
+
const configMap = userAttributes.entries.find(entry => entry.key === 'config') || { key: 'config' }
|
|
120
|
+
configMap.value = configMap.value || { entries: [] }
|
|
121
|
+
configMap.value.entries.push({ key: `com.openexchange.capability.${cap}`, value })
|
|
122
|
+
|
|
123
|
+
return resellerUserService.change(this.context, { id: this.get('id'), userAttributes })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
doesntHaveCapability (capability) {
|
|
127
|
+
this._setCapability(capability, false)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get (key) {
|
|
131
|
+
return this.userdata[key]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// eslint-disable-next-line camelcase
|
|
135
|
+
static getRandom ({ name = 'test.user', password = util.getDefaultUserPassword(), sur_name = '', given_name = 'User' } = {}) {
|
|
136
|
+
const id = short.generate().slice(0, 9).toLowerCase()
|
|
137
|
+
// eslint-disable-next-line camelcase
|
|
138
|
+
if (!sur_name) sur_name = id
|
|
139
|
+
return {
|
|
140
|
+
name: `${name}-${id}`,
|
|
141
|
+
password,
|
|
142
|
+
// eslint-disable-next-line camelcase
|
|
143
|
+
sur_name,
|
|
144
|
+
// eslint-disable-next-line camelcase
|
|
145
|
+
given_name
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
get login () {
|
|
150
|
+
const { name, password } = this.userdata
|
|
151
|
+
return { name, password }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static async create (user = User.getRandom(), ctx = {}, preprovisioned = false) {
|
|
155
|
+
if (preprovisionedUsers.length > 0 && !preprovisioned) {
|
|
156
|
+
let index = 0
|
|
157
|
+
if (ctx.id) index = preprovisionedUsers.findIndex(u => u.context.id === ctx.id)
|
|
158
|
+
if (index !== -1) {
|
|
159
|
+
const currentUser = preprovisionedUsers[index]
|
|
160
|
+
preprovisionedUsers.splice(index, 1)
|
|
161
|
+
usersToRemove.push(currentUser)
|
|
162
|
+
users.push(currentUser)
|
|
163
|
+
return currentUser
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!ctx.id || !ctx.name) {
|
|
168
|
+
const contexts = require('../contexts/reseller')()
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
ctx = contexts[0] || await contexts.create()
|
|
171
|
+
}
|
|
172
|
+
const usrdata = {
|
|
173
|
+
...{
|
|
174
|
+
primaryEmail: `${user.name}@${util.mxDomain()}`,
|
|
175
|
+
display_name: user.name,
|
|
176
|
+
sur_name: user.sur_name,
|
|
177
|
+
given_name: user.given_name,
|
|
178
|
+
name: user.name,
|
|
179
|
+
email1: `${user.name}@${util.mxDomain()}`,
|
|
180
|
+
password: user.password,
|
|
181
|
+
imapLogin: user.name,
|
|
182
|
+
imapServer: user.imapServer || util.imapServer(),
|
|
183
|
+
smtpServer: user.smtpServer || util.smtpServer()
|
|
184
|
+
},
|
|
185
|
+
...user
|
|
186
|
+
}
|
|
187
|
+
event.emit(event.provisioning.user.create, usrdata, ctx)
|
|
188
|
+
|
|
189
|
+
let data
|
|
190
|
+
try {
|
|
191
|
+
data = await resellerUserService.createByModuleAccessName(ctx, usrdata)
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (!/FLD-0038/.test(error.faultstring)) {
|
|
194
|
+
throw new util.PropagatedError(error)
|
|
195
|
+
}
|
|
196
|
+
return new Promise(function (resolve, reject) {
|
|
197
|
+
setTimeout(function () {
|
|
198
|
+
User.create(user, ctx).then(resolve, reject)
|
|
199
|
+
}, 1000)
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
const newUser = new User({ user: data, context: ctx })
|
|
203
|
+
if (!preprovisioned) users.push(newUser)
|
|
204
|
+
if (preprovisioned) preprovisionedUsers.push(newUser)
|
|
205
|
+
event.emit(event.provisioning.user.created, newUser)
|
|
206
|
+
return newUser
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
change (userdata) {
|
|
210
|
+
return resellerUserService.change(this.context, userdata)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
static async removeAll () {
|
|
214
|
+
// Make sure to also remove the preprovisioned users
|
|
215
|
+
users.splice(0, 0, ...usersToRemove.splice(0, usersToRemove.length))
|
|
216
|
+
let user = users.pop()
|
|
217
|
+
while (user) {
|
|
218
|
+
await user.remove()
|
|
219
|
+
user = users.pop()
|
|
220
|
+
}
|
|
221
|
+
return users
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = () => {
|
|
226
|
+
return new Proxy(users, {
|
|
227
|
+
// act like an array if an index is being used
|
|
228
|
+
get (target, prop) {
|
|
229
|
+
if (prop in target) { return target[prop] }
|
|
230
|
+
return User[prop]
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
*
|
|
5
|
+
* This code is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
17
|
+
*
|
|
18
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/* eslint-disable camelcase */
|
|
22
|
+
const event = require('../event')
|
|
23
|
+
|
|
24
|
+
const users = []
|
|
25
|
+
const util = require('../util')
|
|
26
|
+
const short = require('short-uuid')
|
|
27
|
+
const userService = require('../soap/services/user')
|
|
28
|
+
|
|
29
|
+
class User {
|
|
30
|
+
constructor (opt) {
|
|
31
|
+
this.userdata = opt.user
|
|
32
|
+
this.context = opt.context
|
|
33
|
+
return new Proxy(this, {
|
|
34
|
+
get (target, prop) {
|
|
35
|
+
return prop in target ? target[prop] : target.userdata[prop]
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async remove () {
|
|
41
|
+
await userService.remove(this.context, this.get('id'))
|
|
42
|
+
users.splice(0, users.length, ...users.filter(u => u !== this))
|
|
43
|
+
event.emit(event.provisioning.user.removed, this)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async hasConfig (key, value) {
|
|
47
|
+
// Config structure is the following { userAttributes: { entries: [{ key: 'config', value: { entries: [{ key: 'key', value: value }] }}]} }
|
|
48
|
+
// Note that the soap lib will replace arrays with a single element by that element
|
|
49
|
+
|
|
50
|
+
// check if config exists and create config path if it does not exist
|
|
51
|
+
const userAttributes = this.userdata.userAttributes = this.userdata.userAttributes || {}
|
|
52
|
+
const entries = userAttributes.entries = userAttributes.entries ? [].concat(userAttributes.entries) : []
|
|
53
|
+
let configEntry = entries.find(entry => entry.key === 'config')
|
|
54
|
+
if (!configEntry) {
|
|
55
|
+
entries.push({ key: 'config', value: {} })
|
|
56
|
+
configEntry = entries[entries.length - 1]
|
|
57
|
+
}
|
|
58
|
+
const config = configEntry.value
|
|
59
|
+
const configEntries = config.entries = config.entries ? [].concat(config.entries) : []
|
|
60
|
+
let targetConfig = configEntries.find(entry => entry.key === key)
|
|
61
|
+
if (!targetConfig) {
|
|
62
|
+
configEntries.push({ key })
|
|
63
|
+
targetConfig = configEntries[configEntries.length - 1]
|
|
64
|
+
}
|
|
65
|
+
targetConfig.value = value
|
|
66
|
+
|
|
67
|
+
return userService.change(this.context, this.userdata)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
hasAlias (alias) {
|
|
71
|
+
// See if this alias has already been declared
|
|
72
|
+
if (this.userdata.aliases.includes(alias)) return
|
|
73
|
+
// If not, add it and save
|
|
74
|
+
this.userdata.aliases.push(alias)
|
|
75
|
+
return userService.change(this.context, this.userdata)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
doesntHaveAlias (alias) {
|
|
79
|
+
const index = this.userdata.aliases.indexOf(alias)
|
|
80
|
+
// See if this alias has already been declared
|
|
81
|
+
if (index < 0) return
|
|
82
|
+
this.userdata.aliases.splice(index)
|
|
83
|
+
return userService.change(this.context, this.userdata)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
hasAccessCombination (accessCombinationName) {
|
|
87
|
+
return userService.changeByModuleAccessName(this.context, this.get('id'), accessCombinationName)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getModuleAccess () {
|
|
91
|
+
return userService.getModuleAccess(this.context, this.get('id'))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async hasModuleAccess (moduleAccess) {
|
|
95
|
+
const currentAccess = await this.getModuleAccess()
|
|
96
|
+
return userService.changeByModuleAccess(this.context, this.get('id'), currentAccess, moduleAccess)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
hasCapability (capsToAdd) {
|
|
100
|
+
return userService.changeCapabilities(this.context, this.get('id'), capsToAdd)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
doesntHaveCapability (capsToRemove) {
|
|
104
|
+
return userService.changeCapabilities(this.context, this.get('id'), undefined, capsToRemove)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get login () {
|
|
108
|
+
return `${this.get('name')}@${this.context.id}`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get (key) {
|
|
112
|
+
return this.userdata[key]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
toJSON () {
|
|
116
|
+
return {
|
|
117
|
+
context: { ...this.context },
|
|
118
|
+
...this.userdata
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static getRandom ({ name = 'test.user', password = util.getDefaultUserPassword(), sur_name = '', given_name = 'User' } = {}) {
|
|
123
|
+
const id = short.generate().slice(0, 9).toLowerCase()
|
|
124
|
+
// eslint-disable-next-line camelcase
|
|
125
|
+
if (!sur_name) sur_name = id
|
|
126
|
+
if (name === 'test.user') name = `${name}-${id}`
|
|
127
|
+
const domain = util.mxDomain()
|
|
128
|
+
return {
|
|
129
|
+
name,
|
|
130
|
+
primaryEmail: `${name}@${domain}`,
|
|
131
|
+
email1: `${name}@${domain}`,
|
|
132
|
+
password,
|
|
133
|
+
sur_name,
|
|
134
|
+
given_name
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static async create (user = this.getRandom(), ctx = {}) {
|
|
139
|
+
const usrdata = Object.assign({
|
|
140
|
+
display_name: user.name,
|
|
141
|
+
imapLogin: user.name,
|
|
142
|
+
imapServer: util.imapServer(),
|
|
143
|
+
smtpServer: util.smtpServer()
|
|
144
|
+
}, user)
|
|
145
|
+
event.emit(event.provisioning.user.create, usrdata, ctx)
|
|
146
|
+
const contexts = require('../contexts/contexts')()
|
|
147
|
+
let userContext
|
|
148
|
+
try {
|
|
149
|
+
// @ts-ignore
|
|
150
|
+
userContext = await contexts.reuse(ctx, ctx.admin, ctx.auth)
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// @ts-ignore
|
|
153
|
+
userContext = await contexts.create(Object.assign({ maxQuota: 1000 }, ctx))
|
|
154
|
+
}
|
|
155
|
+
const data = await userService.create(userContext, usrdata)
|
|
156
|
+
const index = users.push(new User({ user: data, context: userContext })) - 1
|
|
157
|
+
event.emit(event.provisioning.user.created, users[index])
|
|
158
|
+
return users[index]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
change (userdata) {
|
|
162
|
+
return userService.change(this.context, userdata)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static async removeAll () {
|
|
166
|
+
let user = users.pop()
|
|
167
|
+
while (user) {
|
|
168
|
+
await user.remove()
|
|
169
|
+
user = users.pop()
|
|
170
|
+
}
|
|
171
|
+
return users
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = () => {
|
|
176
|
+
return new Proxy(users, {
|
|
177
|
+
// act like an array if an index is being used
|
|
178
|
+
get (target, prop) {
|
|
179
|
+
if (prop in target) { return target[prop] }
|
|
180
|
+
return User[prop]
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
}
|
package/src/users.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
*
|
|
5
|
+
* This code is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
17
|
+
*
|
|
18
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
let moduleToLoad
|
|
22
|
+
|
|
23
|
+
if (process.env.PROVISIONING_API === 'reseller') {
|
|
24
|
+
moduleToLoad = './users/reseller'
|
|
25
|
+
} else {
|
|
26
|
+
moduleToLoad = './users/users'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = require(moduleToLoad)
|