61fe5e33 by Adam Heath

Hook based pushing of account data to keycloak.

1 parent b355489b
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!--
3 This software is in the public domain under CC0 1.0 Universal plus a
4 Grant of Patent License.
5
6 To the extent possible under law, the author(s) have dedicated all
7 copyright and related and neighboring rights to this software to the
8 public domain worldwide. This software is distributed without any
9 warranty.
10
11 You should have received a copy of the CC0 Public Domain Dedication
12 along with this software (see the LICENSE.md file). If not, see
13 <http://creativecommons.org/publicdomain/zero/1.0/>.
14 -->
15 <eecas xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/entity-eca-3.xsd">
16 <!--
17 moqui.security.UserGroup
18 moqui.security.UserGroupMember
19 moqui.security.UserGroupPermission
20 moqui.security.UserPermission
21 mantle.party.PartyClassification
22
23 moqui.security.UserPermission -> userPermission:$id
24 mantle.party.PartyClassification -> partyClassification:$id
25 mantle.party.RoleType -> roleType:$id
26
27 moqui.security.UserAccount
28
29 moqui.security.UserAccount
30 mantle.party.Party
31 mantle.party.PartyClassificationAppl
32 mantle.party.PartyRole
33 mantle.party.Person
34 mantle.party.RoleType
35 mantle.party.contact.ContactMech
36 mantle.party.contact.TelecomNumber
37 mantle.party.contact.PostalAddress
38 mantle.party.contact.PartyContactMech
39
40 Future use:
41 mantle.party.PartyRelationship
42 -->
43 <eeca id="ContactMech" entity="mantle.party.contact.ContactMech" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
44 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'ContactMech', value: contactMechId]"/></actions>
45 </eeca>
46 <eeca id="Party" entity="mantle.party.Party" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
47 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'Party', value: partyId]"/></actions>
48 </eeca>
49 <eeca id="PartyClassification" entity="mantle.party.PartyClassification" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
50 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'PartyClassification', value: partyClassificationId]"/></actions>
51 </eeca>
52 <eeca id="PartyClassificationAppl" entity="mantle.party.PartyClassificationAppl" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
53 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'Party', value: partyId]"/></actions>
54 </eeca>
55 <eeca id="PartyContactMech" entity="mantle.party.contact.PartyContactMech" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
56 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'Party', value: partyId]"/></actions>
57 </eeca>
58 <eeca id="PartyRole" entity="mantle.party.PartyRole" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
59 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'Party', value: partyId]"/></actions>
60 </eeca>
61 <eeca id="Person" entity="mantle.party.Person" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
62 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'Party', value: partyId]"/></actions>
63 </eeca>
64 <eeca id="PostalAddress" entity="mantle.party.contact.PostalAddress" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
65 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'ContactMech', value: contactMechId]"/></actions>
66 </eeca>
67 <eeca id="RoleType" entity="mantle.party.RoleType" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
68 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'RoleType', value: roleTypeId]"/></actions>
69 </eeca>
70 <eeca id="TelecomNumber" entity="mantle.party.contact.TelecomNumber" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
71 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'ContactMech', value: contactMechId]"/></actions>
72 </eeca>
73 <eeca id="UserAccount" entity="moqui.security.UserAccount" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
74 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'UserAccount', value: userId]"/></actions>
75 </eeca>
76 <eeca id="UserGroup" entity="moqui.security.UserGroup" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
77 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'UserGroup', value: userGroupId]"/></actions>
78 </eeca>
79 <eeca id="UserGroupMember" entity="moqui.security.UserGroupMember" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
80 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'UserAccount', value: userId]"/></actions>
81 </eeca>
82 <eeca id="UserGroupPermission" entity="moqui.security.UserGroupPermission" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
83 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'UserGroup', value: userGroupId]"/></actions>
84 </eeca>
85 <eeca id="UserPermission" entity="moqui.security.UserPermission" on-create="true" on-update="true" on-delete="true" run-on-error="false" get-entire-entity="false" get-original-value="false">
86 <actions><service-call name="keycloak.HookServices.handle#EntityUpdate" in-map="[entityName: 'UserPermission', value: userPermissionId]"/></actions>
87 </eeca>
88 </eecas>
1 <screen-extend xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-3.xsd">
2
3 <form-single name="UpdateUserAccount">
4 <field name="externalUserId"><default-field><text-line size="60"/></default-field></field>
5 </form-single>
6
7 </screen-extend>
1 <screen-extend xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-3.xsd">
2
3 <form-single name="UpdateUserAccount">
4 <field name="externalUserId"><default-field><text-line size="60"/></default-field></field>
5 <field-layout>
6 <fields-not-referenced/>
7 <field-row><field-ref name="externalUserId"/></field-row>
8 <field-row><field-ref name="requirePasswordChange"/><field-ref name="disabled"/></field-row>
9 <field-row><field-ref name="successiveFailedLogins"/><field-ref name="disabledDateTime"/></field-row>
10 <field-ref name="submitButton"/>
11 </field-layout>
12 </form-single>
13 <!--
14
15
16 -->
17 </screen-extend>
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!--
3 This software is in the public domain under CC0 1.0 Universal plus a
4 Grant of Patent License.
5
6 To the extent possible under law, the author(s) have dedicated all
7 copyright and related and neighboring rights to this software to the
8 public domain worldwide. This software is distributed without any
9 warranty.
10
11 You should have received a copy of the CC0 Public Domain Dedication
12 along with this software (see the LICENSE.md file). If not, see
13 <http://creativecommons.org/publicdomain/zero/1.0/>.
14 -->
15 <secas xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-eca-3.xsd">
16 <!-- Invoice GL Posting -->
17 <seca id="updatePasswordInternal" service="org.moqui.impl.UserServices.update#PasswordInternal" when="post-service" run-on-error="false">
18 <condition><expression>updateSuccessful</expression></condition>
19 <actions><service-call name="keycloak.HookServices.handle#UpdatePasswordInternal" in-map="context"/></actions>
20 </seca>
21 <!--
22 <service verb="update" noun="PasswordInternal" authenticate="false" allow-remote="false">
23 <in-parameters>
24 <parameter name="userId" required="true"/>
25 <parameter name="newPassword" required="true"/>
26 <parameter name="newPasswordVerify" required="true"/>
27 <parameter name="requirePasswordChange" default-value="N"/>
28 </in-parameters>
29 <out-parameters>
30 <parameter name="passwordIssues" type="Boolean"/>
31 <parameter name="updateSuccessful" type="Boolean"/>
32 </out-parameters>
33 -->
34 </secas>
1 import javax.transaction.Synchronization
2 import javax.transaction.xa.XAResource
3 import javax.transaction.xa.Xid
4
5 import groovy.transform.Field
6
7 import org.slf4j.Logger
8 import org.slf4j.LoggerFactory
9
10 import org.moqui.util.RestClient
11 import org.moqui.util.ObjectUtilities
12 import org.moqui.context.ExecutionContext
13 import org.moqui.entity.EntityCondition
14 import org.moqui.entity.EntityFind
15 import org.moqui.entity.EntityList
16 import org.moqui.entity.EntityValue
17
18 @Field Logger logger = LoggerFactory.getLogger(getClass().getName())
19 //'HookServices')
20
21 class HookSynchronization implements Synchronization {
22 private final ExecutionContext ec;
23
24 private static class EntityToken {
25 protected final String entityName
26 protected final String keyName
27 protected final Object keyValue
28 protected final Map<String, Object> extraParameters = new HashMap<String, Object>()
29
30 protected EntityToken(String entityName, String keyName, Object keyValue) {
31 this.entityName = entityName
32 this.keyName = keyName
33 this.keyValue = keyValue
34 }
35
36 public boolean equals(Object other) {
37 if (!(other instanceof EntityToken)) {
38 return false
39 }
40 EntityToken that = (EntityToken) other
41 return entityName.equals(that.entityName) && keyName.equals(that.keyName) && keyValue.equals(that.keyValue)
42 }
43
44 public int hashCode() {
45 return entityName.hashCode() ^ keyName.hashCode() ^ keyValue.hashCode()
46 }
47 }
48
49 private Map<EntityToken, Map<String, Object>> updates = new LinkedHashMap<>()
50
51 protected HookSynchronization(ExecutionContext ec) {
52 this.ec = ec
53 }
54
55 protected void add(String entityName, String keyName, Object keyValue, Map<String, Object> extraParameters) {
56 EntityToken entityToken = new EntityToken(entityName, keyName, keyValue)
57 // This always move the value to the end of the list
58 Map<String, Object> existingExtraParameters = updates.remove(entityToken)
59 Map<String, Object> newExtraParameters = [:]
60 if (existingExtraParameters) {
61 newExtraParameters.putAll(existingExtraParameters)
62 }
63 if (extraParameters) {
64 newExtraParameters.putAll(extraParameters)
65 }
66 updates.put(entityToken, newExtraParameters)
67 }
68
69 @Override
70 public void beforeCompletion() {
71 List<Map<String, Object>> updates = []
72 for (Map.Entry<EntityToken, Map<String, Object>> entry: this.updates.entrySet()) {
73 EntityToken token = entry.getKey()
74 Map<String, Object> extraParameters = entry.getValue()
75 updates.add([
76 entityName: token.entityName,
77 keyName: token.keyName,
78 keyValue: token.keyValue,
79 extraParameters: extraParameters,
80 ])
81 }
82 ec.getService().async().name("keycloak.HookServices.process#Updates").parameter('updates', updates).call()
83 }
84
85 @Override
86 public void afterCompletion(int status) {
87
88 }
89 }
90
91 /*
92 class HookXAResource implements XAResource {
93 private static class EntityPk {
94 protected String entityName;
95 protected Map<String, Object> pk;
96
97 protected EntityPk(EntityValue entity) {
98 this.entityName = entity.getEntityName()
99 this.pk = entity.getPrimaryKeys()
100 }
101
102 public boolean equals(Object other) {
103 if (!(object instanceof EntityPk)) {
104 return false
105 }
106 EntityPk that = (EntityPk) other
107 return entityName.equals(that.entityName) && pk.equals(that.pk)
108 }
109
110 public int hashCode() {
111 return entityName.hashCode ^ pk.hashCode()
112 }
113 }
114
115 private Map<Xid, Set<EntityPk>> modifiedValues = new HashMap<>()
116 private int timeout = 60
117
118 @Override
119 public void commit(Xid xid, boolean onePhase) {
120
121 }
122
123 @Override
124 public void end(Xid xid, int flags) {
125 if (flags & TMFAIL) {
126
127 } else if (flags & TMSUSPEND) {
128
129 } else if (flags & TMSUCCESS) {
130
131 }
132 }
133
134 @Override
135 public void forget(Xid xid) {
136 modifiedValues.remove(xid)
137 }
138
139 @Override
140 public int getTransactionTimeout() {
141 return timeout
142 }
143
144 @Override
145 public boolean isSameRM(XAResource xar) {
146 return getClass().equals(xar.getClass())
147 }
148
149 @Override
150 public int prepare(Xid xid) {
151 if (!keycloak.connected()) {
152 throw new XAException(XAException.XAERR_RMFAIL)
153 }
154 }
155
156 @Override
157 public Xid[] recover(int flag) {
158
159 }
160
161 @Override
162 public void rollback(Xid xid) {
163
164 }
165
166 @Override
167 public boolean setTransactionTimeout(int timeout) {
168 this.timeout = timeout
169 }
170
171 @Override
172 public void start(Xid xid, int flags) {
173
174 }
175
176 }
177 */
178
179 HookSynchronization getHookSync() {
180 HookSynchronization hookSync = ec.transaction.getActiveSynchronization(getClass().getName())
181 if (hookSync == null) {
182 hookSync = new HookSynchronization(ec)
183 ec.transaction.putAndEnlistActiveSynchronization(getClass().getName(), hookSync)
184 }
185 return hookSync
186 }
187
188 Map<String, Object> handleEntityUpdate() {
189 logger.info("handleEntityUpdate: ${context.entityName}[${context.value}]")
190 logger.info("context: ${context}")
191 ExecutionContext ec = context.ec
192 Map<String, Object> contextRoot = ec.getContextRoot()
193 Map<String, Set<Object>> toRegister = contextRoot['KeycloakEntityRegistrations']
194 logger.info("toRegister: ${toRegister}")
195 if (toRegister == null) {
196 toRegister = contextRoot['KeycloakEntityRegistrations'] = [:]
197 }
198
199 HookSynchronization hookSync = getHookSync()
200
201 List<Map<String, Object>> queue = [[entityName: context.entityName, value: context.value]]
202
203 while (!queue.isEmpty()) {
204 Map<String, Object> entry = queue.remove(0)
205 logger.info("processing entry: ${entry}")
206 String entityName = entry.entityName
207 Object value = entry.value
208 Set<Object> entityRegistrations = toRegister[entityName]
209 if (entityRegistrations == null) {
210 entityRegistrations = toRegister[entityName] = new HashSet()
211 entityRegistrations.add(value)
212 } else if (entityRegistrations.contains(value)) {
213 continue
214 }
215 String keyName
216 switch (entityName) {
217 case 'Party':
218 EntityList userAccounts = ec.entity.find('UserAccount').condition('partyId', value).list()
219 for (EntityValue userAccount: userAccounts) {
220 queue.add([entityName: 'UserAccount', value: userAccount.userId])
221 }
222 continue
223 case 'ContactMech':
224 EntityList partyContactMechs = ec.entity.find('PartyContactMech').condition('contactMechId', value).list()
225 for (EntityValue partyContactMech: partyContactMechs) {
226 queue.add([entityName: 'Party', value: partyContactMech.partyId])
227 }
228 continue
229 case 'UserAccount':
230 keyName = 'userId'
231 break
232 case 'UserGroup':
233 keyName = 'userGroupId'
234 break
235 case 'UserGroupMember':
236 keyName = 'userGroupId'
237 break
238 case 'UserPermission':
239 keyName = 'userPermissionId'
240 break
241 case 'PartyClassification':
242 keyName = 'partyClassificationId'
243 break
244 case 'RoleType':
245 keyName = 'roleTypeId'
246 break
247 }
248 ec.getLogger().info("Registered synchronization for ${entityName}: [${value}]")
249 hookSync.add(entityName, keyName, value, [:])
250 //ec.getService().special().name("keycloak.KeycloakServices.send#${entityName}").parameter(keyName, value).registerOnCommit()
251 }
252 }
253
254 Map<String, Object> handleUpdatePasswordInternal() {
255 String userId = ec.context.userId
256 String newPassword = ec.context.newPassword
257 Boolean requirePasswordChange = ec.context.requirePasswordChange
258 ec.getLogger().info("Registered synchronization for update#PasswordInternal: [${userId}]")
259 HookSynchronization hookSync = getHookSync()
260 hookSync.add('UserAccount', 'userId', userId, [
261 newPassword: newPassword,
262 requirePasswordChange: requirePasswordChange,
263 ])
264 }
265
266 Map<String, Object> processUpdates() {
267 List<Map<String, Object>> updates = context.updates
268
269 for (Map<String, Object> update: updates) {
270 Map<String, Object> parameters = [:]
271 parameters[update.keyName] = update.keyValue
272 parameters.putAll(update.extraParameters)
273 ec.getService().sync().name("keycloak.KeycloakServices.send#${update.entityName}").parameters(parameters).call()
274 }
275 return [:]
276 }
277
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!--
3 This software is in the public domain under CC0 1.0 Universal plus a
4 Grant of Patent License.
5
6 To the extent possible under law, the author(s) have dedicated all
7 copyright and related and neighboring rights to this software to the
8 public domain worldwide. This software is distributed without any
9 warranty.
10
11 You should have received a copy of the CC0 Public Domain Dedication
12 along with this software (see the LICENSE.md file). If not, see
13 <http://creativecommons.org/publicdomain/zero/1.0/>.
14 -->
15 <services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-definition-3.xsd">
16
17 <service verb="handle" noun="EntityUpdate" type="script" location="component://moqui-keycloak/service/keycloak/HookServices.groovy" method="handleEntityUpdate" authenticate="anonymous-all">
18 <in-parameters>
19 <parameter name="entityName" type="String" required="true"/>
20 <parameter name="value" type="Object" required="true"/>
21 </in-parameters>
22 </service>
23
24 <service verb="handle" noun="UpdatePasswordInternal" type="script" location="component://moqui-keycloak/service/keycloak/HookServices.groovy" method="handleUpdatePasswordInternal" authenticate="anonymous-all">
25 <in-parameters>
26 <parameter name="userId" required="true"/>
27 <parameter name="newPassword" required="true"/>
28 <parameter name="requirePasswordChange"/>
29 </in-parameters>
30 </service>
31
32 <service verb="process" noun="Updates" type="script" location="component://moqui-keycloak/service/keycloak/HookServices.groovy" method="processUpdates" authenticate="anonymous-all">
33 <in-parameters>
34 <parameter name="updates" type="List" required="true"/>
35 </in-parameters>
36 </service>
37 </services>
...@@ -2,21 +2,12 @@ import groovy.json.JsonOutput ...@@ -2,21 +2,12 @@ import groovy.json.JsonOutput
2 import groovy.transform.Field 2 import groovy.transform.Field
3 3
4 import java.sql.Timestamp 4 import java.sql.Timestamp
5 import java.util.Base64
6 import java.util.UUID
7 import javax.crypto.Cipher;
8 import javax.crypto.SecretKey;
9 import javax.crypto.SecretKeyFactory;
10 import javax.crypto.spec.PBEKeySpec;
11 import javax.crypto.spec.PBEParameterSpec;
12 5
13 import org.slf4j.Logger 6 import org.slf4j.Logger
14 import org.slf4j.LoggerFactory 7 import org.slf4j.LoggerFactory
15 8
16 import groovy.json.JsonSlurper 9 import groovy.json.JsonSlurper
17 10
18 import org.moqui.util.RestClient
19 import org.moqui.util.ObjectUtilities
20 import org.moqui.context.ExecutionContext 11 import org.moqui.context.ExecutionContext
21 import org.moqui.entity.EntityCondition 12 import org.moqui.entity.EntityCondition
22 import org.moqui.entity.EntityFind 13 import org.moqui.entity.EntityFind
...@@ -25,19 +16,22 @@ import org.moqui.entity.EntityValue ...@@ -25,19 +16,22 @@ import org.moqui.entity.EntityValue
25 16
26 import org.moqui.keycloak.KeycloakToolFactory 17 import org.moqui.keycloak.KeycloakToolFactory
27 18
28
29 import org.keycloak.OAuth2Constants 19 import org.keycloak.OAuth2Constants
30 import org.keycloak.admin.client.Keycloak 20 import org.keycloak.admin.client.Keycloak
31 import org.keycloak.admin.client.KeycloakBuilder 21 import org.keycloak.admin.client.KeycloakBuilder
32 import org.keycloak.admin.client.resource.ClientResource 22 import org.keycloak.admin.client.resource.ClientResource
23 import org.keycloak.admin.client.resource.GroupsResource
33 import org.keycloak.admin.client.resource.RealmResource 24 import org.keycloak.admin.client.resource.RealmResource
34 import org.keycloak.admin.client.resource.RoleMappingResource 25 import org.keycloak.admin.client.resource.RoleMappingResource
35 import org.keycloak.admin.client.resource.RoleScopeResource 26 import org.keycloak.admin.client.resource.RoleScopeResource
27 import org.keycloak.admin.client.resource.RoleResource
36 import org.keycloak.admin.client.resource.RolesResource 28 import org.keycloak.admin.client.resource.RolesResource
37 import org.keycloak.admin.client.resource.ServerInfoResource 29 import org.keycloak.admin.client.resource.ServerInfoResource
38 import org.keycloak.admin.client.resource.UserResource 30 import org.keycloak.admin.client.resource.UserResource
39 import org.keycloak.admin.client.resource.UsersResource 31 import org.keycloak.admin.client.resource.UsersResource
40 import org.keycloak.representations.idm.ClientMappingsRepresentation 32 import org.keycloak.representations.idm.ClientMappingsRepresentation
33 import org.keycloak.representations.idm.CredentialRepresentation
34 import org.keycloak.representations.idm.GroupRepresentation
41 import org.keycloak.representations.idm.MappingsRepresentation 35 import org.keycloak.representations.idm.MappingsRepresentation
42 import org.keycloak.representations.idm.RoleRepresentation 36 import org.keycloak.representations.idm.RoleRepresentation
43 import org.keycloak.representations.idm.UserRepresentation 37 import org.keycloak.representations.idm.UserRepresentation
...@@ -53,106 +47,320 @@ String keycloakToJson(Object o) { ...@@ -53,106 +47,320 @@ String keycloakToJson(Object o) {
53 return JsonSerialization.writeValueAsString(o) 47 return JsonSerialization.writeValueAsString(o)
54 } 48 }
55 49
56 Map<String, Object> buildClientConsent() { 50 void cleanupKeycloak() {
57 return [ 51 Map<String, Object> sharedMap = ec.getContextRoot()
58 clientId: null, 52 Keycloak keycloak = sharedMap['keycloak']
59 createdDate: null, // int64 53 if (keycloak != null) {
60 grantedClientScopes: [], // array[string] 54 keycloak.close()
61 lastUpdatedData: null, // int64 55 }
62 ] 56 Iterator<String> keyIt = sharedMap.keySet().iterator()
57 while (keyIt.hasNext()) {
58 String key = keyIt.next()
59 if (key == 'keycloak' || key.startsWith('keycloak:')) {
60 keyIt.remove()
61 }
62 }
63 }
64
65 Keycloak getKeycloak() {
66 Map<String, Object> sharedMap = ec.getContextRoot()
67 KeycloakToolFactory keycloakToolFactory = ec.getFactory().getToolFactory('Keycloak')
68 Keycloak keycloak = sharedMap['keycloak']
69 if (keycloak == null) {
70 keycloak = keycloakToolFactory.getInstance()
71 sharedMap['keycloak'] = keycloak
72 }
73 return keycloak
63 } 74 }
64 75
65 Map<String, Object> buildCredential() { 76 RealmResource getKeycloakRealm() {
66 return [ 77 Map<String, Object> sharedMap = ec.getContextRoot()
67 createdDate: null, // int64 78 RealmResource keycloakRealm = sharedMap['keycloak:realm']
68 credentialData: null, // string 79 if (!realm) {
69 id: null, // string 80 Keycloak keycloak = getKeycloak()
70 priority: null, // int32 81 KeycloakToolFactory keycloakToolFactory = ec.getFactory().getToolFactory('Keycloak')
71 secretData: null, // string 82 keycloakRealm = keycloak.realm(keycloakToolFactory.getRealmName())
72 temporary: null, // boolean 83 sharedMap['keycloak:realm'] = keycloakRealm
73 type: null, // string 84 }
74 userLabel: null, // string 85 return keycloakRealm
75 value: null, // string
76 ]
77 } 86 }
78 87
79 Map<String, Object> buildFederatedIdentiy() { 88 String getKeycloakClientId() {
80 return [ 89 Map<String, Object> sharedMap = ec.getContextRoot()
81 identityProvider: null, // string 90 String keycloakClientId = sharedMap['keycloak:clientId']
82 userId: null, // string 91 if (!keycloakClientId) {
83 userName: null, // string 92 RealmResource keycloakRealm = getKeycloakRealm()
84 ] 93 KeycloakToolFactory keycloakToolFactory = ec.getFactory().getToolFactory('Keycloak')
94 keycloakClientId = keycloakRealm.clients().findByClientId(keycloakToolFactory.getClientId()).get(0).getId()
95 sharedMap['keycloak:clientId'] = keycloakClientId
96 }
97 return keycloakClientId
85 } 98 }
86 99
87 Map<String, Object> buildKeycloakUser(String userId) { 100 ClientResource getKeycloakClient() {
88 EntityValue userLogin = ec.entity.find('UserLogin').condition('userId', userId) 101 Map<String, Object> sharedMap = ec.getContextRoot()
89 102 ClientResource keycloakClient = sharedMap['keycloak:client']
90 Map<String, Object> keycloakUser = [ 103 if (!keycloakClient) {
91 access: [:], 104 String keycloakClientId = getKeycloakClientId()
92 attributes: [:], 105 RealmResource realm = getKeycloakRealm()
93 clientConsents: [], 106 keycloakClient = realm.clients().get(keycloakClientId)
94 clientRoles: [:], 107 sharedMap['keycloak:client'] = keycloakClient
95 createdTimestamp: null, // int64 108 }
96 credentials: [], 109 return keycloakClient
97 disableableCredentialTypes: [], // array[string]
98 email: null, // string
99 emailVerified: null, // boolean
100 enabled: null, // boolean
101 federatedIdentities: [],
102 federationLink: null, // string
103 firstName: null,
104 groups: [], // array[string]
105 id: null, // string
106 lastName: null,
107 notBefore: null, // int32
108 origin: null, // string
109 realmRoles: [], // array[string]
110 requiredActions: [], // array[string]
111 self: null, // string
112 serviceClientId: null, // string
113 username: null, // string
114 ]
115
116 return keycloakUser
117 } 110 }
118 111
119 Map<String, Object> onUpdateEmailAddress() { 112 Map<String, Object> syncUserAccount() {
113 String userId = context.userId
114 String newPassword = context.newPassword
120 115
116 try {
117 EntityValue userAccount = ec.getEntity().find('UserAccount').condition('userId', userId).one()
118 if (!userAccount.username) {
119 ec.message.addMessage("Skipping userAccount with empty username: ${userId})")
120 return
121 }
122 RealmResource realmResource = getKeycloakRealm()
123 UsersResource usersResource = realmResource.users()
124
125 if (userAccount.externalUserId == null) {
126 logger.info("Searching for existing user: ${userAccount.username}")
127 List<UserRepresentation> existingByUsername = usersResource.search(userAccount.username, true)
128 logger.info("Found: ${existingByUsername}")
129 if (!existingByUsername.isEmpty()) {
130 // FIXME: disable eeca
131 boolean reenableEeca = ec.artifactExecutionFacade.disableEntityEca()
132 try {
133 userAccount.externalUserId = existingByUsername[0].getId()
134 userAccount.store()
135 } finally {
136 if (reenableEeca) ec.artifactExecutionFacade.enableEntityEca()
137 }
138 }
139 }
140 if (userAccount.externalUserId == null) {
141 UserRepresentation newKeycloakUser = new UserRepresentation()
142 newKeycloakUser.setUsername(userAccount.username)
143 newKeycloakUser.setEnabled(false)
144 newKeycloakUser.setEmail(userAccount.emailAddress)
145 Object response = usersResource.create(newKeycloakUser)
146 logger.info("create response: ${keycloakToJson(response)}")
147 logger.info("status=${response.status}")
148 if (response.status == 201) {
149 List<UserRepresentation> newlyCreatedUserList = usersResource.search(userAccount.username)
150 if (newlyCreatedUserList.size() == 1) {
151 // FIXME: disable eeca
152 boolean reenableEeca = ec.artifactExecutionFacade.disableEntityEca()
153 try {
154 userAccount.externalUserId = newlyCreatedUserList[0].getId()
155 userAccount.store()
156 } finally {
157 if (reenableEeca) ec.artifactExecutionFacade.enableEntityEca()
158 }
159 }
160 }
161 if (userAccount.externalUserId == null) {
162 ec.message.addError("Couldn't create keycloak user for: ${userAccount.username})")
163 return
164 }
165 }
166 String keycloakUserId = userAccount.externalUserId
167 logger.info("Would update: keycloak(${keycloakUserId})")
168 updateUser(keycloakUserId, userId, newPassword != null)
121 169
170 if (newPassword != null) {
171 UserResource userResource = usersResource.get(keycloakUserId)
172 List<CredentialRepresentation> credentials = userResource.credentials()
173 for (CredentialRepresentation credential: credentials) {
174 logger.info("keycloak(${keycloakUserId}) credential=${keycloakToJson(credential)}")
175 }
176 CredentialRepresentation newPasswordRepr = new CredentialRepresentation()
177 newPasswordRepr.setType(CredentialRepresentation.PASSWORD)
178 newPasswordRepr.setValue(newPassword)
179 newPasswordRepr.setTemporary(false)
180 userResource.resetPassword(newPasswordRepr)
181 }
182
183 } finally {
184 cleanupKeycloak()
185 }
122 } 186 }
123 187
124 Map<String, Object> joinUserAccountToKeycloak() { 188 Map<String, Object> updatePasswordInternal() {
125 String userId = context.userId 189 String userId = context.userId
190 String newPassword = context.newPassword
191 logger.info("updatePasswordInternal: userId:${userId} newPassword:${newPassword}")
126 192
127 String externalUserId = context.externalUserId 193 try {
128 if (externalUserId && externalUserId.startsWith('keycloak:')) { 194 EntityValue userAccount = ec.getEntity().find('UserAccount').condition('userId', userId).one()
129 // Already joined 195 String keycloakUserId = userAccount.externalUserId
130 return [:] 196 RealmResource realmResource = getKeycloakRealm()
197 UsersResource usersResource = realmResource.users()
198 UserResource userResource = usersResource.get(keycloakUserId)
199 List<CredentialRepresentation> credentials = userResource.credentials()
200 for (CredentialRepresentation credential: credentials) {
201 logger.info("keycloak(${keycloakUserId}) credential=${keycloakToJson(credential)}")
202 }
203 CredentialRepresentation newPasswordRepr = new CredentialRepresentation()
204 newPasswordRepr.setType(CredentialRepresentation.PASSWORD)
205 newPasswordRepr.setValue(newPassword)
206 newPasswordRepr.setTemporary(false)
207 userResource.resetPassword(newPasswordRepr)
208
209 } finally {
210 cleanupKeycloak()
131 } 211 }
212 }
213
214 // This does not add in the composite child roles
215 RoleResource syncUserGroup(String userGroupId) {
216 EntityValue userGroup = ec.getEntity().find('UserGroup').condition('userGroupId', userGroupId).one()
217 return configureClientRoleResource('UserGroup:' + userGroupId, userGroup.description, null, null)
218 }
132 219
133 Keycloak keycloak = KeycloakToolFactory.getInstance() 220
221 Map<String, Object> syncUserGroup() {
222 String userGroupId = context.userGroupId
134 223
135 try { 224 try {
136 RealmResource realm = keycloak.realm('master') 225 EntityValue userGroup = ec.getEntity().find('UserGroup').condition('userGroupId', userGroupId).one()
137 UsersResource users = realm.users() 226 String keycloakRoleName = 'UserGroup:' + userGroupId
138 for (UserRepresentation user: users.list()) { 227 Set<RoleRepresentation> wantedRoles = new HashSet<RoleRepresentation>()
139 logger.info('keycloak user: ' + keycloakToJson(user)) 228
140 } 229 List<EntityValue> userGroupPermissionList = ec.entity.find('UserGroupPermission')
230 .condition('userGroupId', userGroupId)
231 .useCache(false)
232 .disableAuthz()
233 .list()
234 .filterByDate('fromDate', 'thruDate', now)
235
236 wantedRoles.addAll(userGroupPermissionList*.userPermissionId.collect { userPermissionId -> syncUserPermission(userPermissionId).toRepresentation() })
237
238 configureClientRoleResource(keycloakRoleName, userGroup.description, wantedRoles, null)
141 } finally { 239 } finally {
142 keycloak.close() 240 cleanupKeycloak()
143 } 241 }
144 242
243 return [:]
145 } 244 }
146 245
147 Map<String, Object> onUpdateUserAccount() { 246 RoleRepresentation getClientRoleRepresentation(String roleName) {
148 String userId = context.userId 247 Map<String, Object> sharedMap = ec.getContextRoot()
149 String externalUserId = context.externalUserId 248 if (sharedMap['keycloak:clientRole[' + roleName + ']']) {
150 if (!(externalUserId && externalUserId.startsWith('keycloak:'))) { 249 return sharedMap['keycloak:clientRole[' + roleName + ']'].toRepresentation()
250 }
251 ClientResource clientResource = getKeycloakClient()
252 RolesResource rolesResource = clientResource.roles()
253 RoleResource roleResource = rolesResource.get(roleName)
254 if (roleResource == null) {
255 rolesResource.create(new RoleRepresentation(roleName, null, false))
256 roleResource = rolesResource.get(roleName)
257 }
258 return roleResource.toRepresentation()
259 }
260
261 RoleResource configureClientRoleResource(String roleName, String description, Set<RoleRepresentation> wantedRoles, Closure updater) {
262 String keycloakClientId = getKeycloakClientId()
263 Map<String, Object> sharedMap = ec.getContextRoot()
264 if (sharedMap['keycloak:clientRole[' + roleName + ']']) {
265 return sharedMap['keycloak:clientRole[' + roleName + ']']
266 }
267 logger.info("configureClientRoleResource: ${roleName}: ${description}")
268 ClientResource clientResource = getKeycloakClient()
269 RolesResource rolesResource = clientResource.roles()
270 RoleResource roleResource = rolesResource.get(roleName)
271 try {
272 roleResource.toRepresentation()
273 } catch (javax.ws.rs.NotFoundException e) {
274 roleResource = null
275 }
276 //RoleRepresentation roleRepresentation = rolesResource.list(roleName, false)?[0]
277
278 boolean updated = false
279 //if (roleRepresentation == null) {
280 if (roleResource == null) {
281 rolesResource.create(new RoleRepresentation(roleName, description, false))
282 roleResource = rolesResource.get(roleName)
283 //roleRepresentation = rolesResource.list(roleName, false).get(0)
284 }
285 RoleRepresentation roleRepresentation = roleResource.toRepresentation()
286 if (roleRepresentation.getDescription() != description) {
287 roleRepresentation.setDescription(description)
288 updated = true
289 }
290 if (updater) {
291 updated |= updater(roleRepresentation)
292 }
293 if (updated) {
294 logger.info("Sending update: ${roleRepresentation}")
295 roleResource.update(roleRepresentation)
296 }
297
298 if (wantedRoles != null) {
299 Set<RoleRepresentation> existingRoles = roleResource.getClientRoleComposites(keycloakClientId)
300 Set<RoleRepresentation> toAdd = new HashSet<RoleRepresentation>(wantedRoles)
301 toAdd.removeAll(existingRoles)
302 Set<RoleRepresentation> toRemove = new HashSet<RoleRepresentation>(existingRoles)
303 toRemove.removeAll(wantedRoles)
304 if (!toRemove.isEmpty()) {
305 roleResource.deleteComposites(toRemove as List)
306 }
307 if (!wantedRoles.isEmpty()) {
308 roleResource.addComposites(wantedRoles as List)
309 }
310 }
311 sharedMap['keycloak:clientRole[' + roleName + ']'] = roleResource
312 return roleResource
313 }
314
315 RoleResource syncUserPermission(String userPermissionId) {
316 EntityValue userPermission = ec.getEntity().find('UserPermission').condition('userPermissionId', userPermissionId).one()
317 return configureClientRoleResource('UserPermission:' + userPermissionId, userPermission.description, null, null)
318 }
319
320 Map<String, Object> syncUserPermission() {
321 String userPermissionId = context.userPermissionId
322
323 try {
324 syncUserPermission(userPermissionId)
325 } finally {
326 cleanupKeycloak()
327 }
328
151 return [:] 329 return [:]
330 }
331
332 RoleResource syncRoleType(String roleTypeId) {
333 EntityValue roleType = ec.getEntity().find('RoleType').condition('roleTypeId', roleTypeId).one()
334 return configureClientRoleResource('RoleType:' + roleTypeId, roleType.description, null, null)
335 }
336
337 Map<String, Object> syncRoleType() {
338 String roleTypeId = context.roleTypeId
339
340 try {
341 syncRoleType(roleTypeId)
342 } finally {
343 cleanupKeycloak()
152 } 344 }
153 String keycloakUserId = externalUserId.substring('keycloak:'.length())
154 345
346 return [:]
347 }
348
349 RoleResource syncPartyClassification(String partyClassificationId) {
350 EntityValue partyClassification = ec.getEntity().find('PartyClassification').condition('partyClassificationId', partyClassificationId).one()
351 return configureClientRoleResource('PartyClassification:' + partyClassificationId, partyClassification.description, null, null)
352 }
353
354 Map<String, Object> syncPartyClassification() {
355 String partyClassificationId = context.partyClassificationId
155 356
357 try {
358 syncPartyClassification(partyClassificationId)
359 } finally {
360 cleanupKeycloak()
361 }
362
363 return [:]
156 } 364 }
157 365
158 Map<String, RoleRepresentation> getClientRoles(RealmResource realm, ClientResource clientResource, Collection<String> roleList) { 366 Map<String, RoleRepresentation> getClientRoles(RealmResource realm, ClientResource clientResource, Collection<String> roleList) {
...@@ -179,42 +387,46 @@ Map<String, RoleRepresentation> getClientRoles(RealmResource realm, ClientResour ...@@ -179,42 +387,46 @@ Map<String, RoleRepresentation> getClientRoles(RealmResource realm, ClientResour
179 String name = resultEntry.getKey() 387 String name = resultEntry.getKey()
180 if (resultEntry.getValue() == null) { 388 if (resultEntry.getValue() == null) {
181 rolesResource.create(new RoleRepresentation(name, "", false)) 389 rolesResource.create(new RoleRepresentation(name, "", false))
182 resultEntry.setValue(rolesResource.get(name)) 390 resultEntry.setValue(rolesResource.get(name).toRepresentation())
183 } 391 }
184 } 392 }
185 393
186 return result 394 return result
187 } 395 }
188 396
397 // return getKeycloakGroup(realm, "moqui:userGroup:${userGroup.userGroupId}", sync)
398 //}
399
400 void updateUser(String keycloakUserId, String userId, boolean enabled) {
189 401
190 void updateUser(RealmResource realm, String keycloakClientId, String keycloakUserId, String userId) { 402 RealmResource realmResource = getKeycloakRealm()
191 String clientId = realm.clients().findByClientId(keycloakClientId).get(0).getId() 403 String keycloakClientId = getKeycloakClientId()
192 logger.info("keycloakClientId=${keycloakClientId} clientId=${clientId}") 404 ClientResource clientResource = getKeycloakClient()
193 ClientResource clientResource = realm.clients().get(clientId)
194 405
195 UsersResource usersResource = realm.users() 406 UsersResource usersResource = realmResource.users()
196 UserResource userResource = usersResource.get(keycloakUserId) 407 UserResource userResource = usersResource.get(keycloakUserId)
197 408
198 Timestamp now = ec.user.nowTimestamp 409 Timestamp now = ec.user.nowTimestamp
199 410
200 List<EntityValue> userPermissionList = ec.entity.find('UserPermissionCheck') 411 EntityValue userAccount = ec.entity.find('UserAccount').condition('userId', userId).useCache(false).disableAuthz().one()
201 .condition('userId', userId) 412 String partyId = userAccount.partyId
202 .useCache(false) 413 EntityValue person = ec.entity.find('Person').condition('partyId', partyId).useCache(false).disableAuthz().one()
203 .disableAuthz() 414
204 .list() 415
205 .filterByDate('groupFromDate', 'groupThruDate', now) 416
206 .filterByDate('permissionFromDate', 'permissionThruDate', now) 417
207 List<String> moquiPermissions = userPermisionList*.userPermissionId.collect { permission -> 'permission:' + permission } 418 Set<RoleRepresentation> wantedClientRoles = new HashSet<RoleRepresentation>()
208 List<EntityValue> userGroupList = ec.entity.find('UserGroupMemberUser') 419
420 List<EntityValue> userGroupMemberList = ec.entity.find('UserGroupMember')
209 .condition('userId', userId) 421 .condition('userId', userId)
210 .useCache(false) 422 .useCache(false)
211 .disableAuthz() 423 .disableAuthz()
212 .list() 424 .list()
213 .filterByDate('fromDate', 'thruDate', now) 425 .filterByDate('fromDate', 'thruDate', now)
214 Set<String> moquiGroups = userGroupList*.userGroupId.collect { group -> 'group:' + group } 426 Set<String> userGroupIds = new HashSet<String>(userGroupMemberList*.userGroupId)
215 moquiGroups.add('group:ALL_USERS') 427 logger.info("userGroupIds: ${userGroupIds}")
216 428 userGroupIds.add('ALL_USERS')
217 Map<String, RoleRepresentation> wantedClientRoles = getClientRoles(realm, clientResource, moquiPermissions + moquiGroups) 429 wantedClientRoles.addAll(userGroupIds.collect { userGroupId -> syncUserGroup(userGroupId).toRepresentation() })
218 430
219 UserRepresentation userRep = userResource.toRepresentation() 431 UserRepresentation userRep = userResource.toRepresentation()
220 Map<String, List<String>> attributes = new HashMap(userRep.getAttributes() ?: [:]) 432 Map<String, List<String>> attributes = new HashMap(userRep.getAttributes() ?: [:])
...@@ -224,6 +436,10 @@ void updateUser(RealmResource realm, String keycloakClientId, String keycloakUse ...@@ -224,6 +436,10 @@ void updateUser(RealmResource realm, String keycloakClientId, String keycloakUse
224 436
225 logger.info("user[$userId]} attributes: " + attributes) 437 logger.info("user[$userId]} attributes: " + attributes)
226 userRep.setAttributes(attributes) 438 userRep.setAttributes(attributes)
439 if (person) {
440 userRep.setFirstName(person.firstName)
441 userRep.setLastName(person.lastName)
442 }
227 443
228 List<RoleRepresentation> toRemove = [] 444 List<RoleRepresentation> toRemove = []
229 RoleMappingResource roleMappingResource = userResource.roles() 445 RoleMappingResource roleMappingResource = userResource.roles()
...@@ -233,64 +449,26 @@ void updateUser(RealmResource realm, String keycloakClientId, String keycloakUse ...@@ -233,64 +449,26 @@ void updateUser(RealmResource realm, String keycloakClientId, String keycloakUse
233 toRemove.add(existingRoleRep) 449 toRemove.add(existingRoleRep)
234 } 450 }
235 } 451 }
236 List<RoleRepresentation> toAdd = wantedClientRoles.values() as List 452 List<RoleRepresentation> toAdd = wantedClientRoles as List
237 logger.info("roles to remove: ${toRemove}") 453 logger.info("roles to remove: ${toRemove}")
238 logger.info("roles to add: ${toAdd}") 454 logger.info("roles to add: ${toAdd}")
239 RoleScopeResource clientRoleScopeResource = roleMappingResource.clientLevel(clientId) 455 RoleScopeResource clientRoleScopeResource = roleMappingResource.clientLevel(keycloakClientId)
240 clientRoleScopeResource.remove(toRemove) 456 clientRoleScopeResource.remove(toRemove)
241 clientRoleScopeResource.add(toAdd) 457 clientRoleScopeResource.add(toAdd)
458 if (enabled) {
459 userRep.setEnabled(true)
460 }
242 461
243 userResource.update(userRep) 462 userResource.update(userRep)
244 Map<String, Object> foo = userResource.impersonate() 463 Map<String, Object> foo = userResource.impersonate()
245 logger.info("impersonate: foo=${foo}") 464 logger.info("impersonate: foo=${foo}")
246 } 465 }
247 466
248
249 Map<String, Object> pushKeycloakUser() {
250 String userId = context.userId
251 EntityValue userLogin = ec.entity.find('UserLogin').condition('userId', userId).one()
252
253
254 List<EntityValue> logins
255 return [:]
256
257 /*
258 <view-entity entity-name="UserPermissionCheck" package="moqui.security">
259 <member-entity entity-alias="UGM" entity-name="moqui.security.UserGroupMember"/>
260 <member-relationship entity-alias="UGP" join-from-alias="UGM" relationship="permissions"/>
261 <alias name="userGroupId" entity-alias="UGM"/>
262 <alias name="userId" entity-alias="UGM"/>
263 <alias name="userPermissionId" entity-alias="UGP"/>
264 <alias name="groupFromDate" entity-alias="UGM" field="fromDate"/>
265 <alias name="groupThruDate" entity-alias="UGM" field="thruDate"/>
266 <alias name="permissionFromDate" entity-alias="UGP" field="fromDate"/>
267 <alias name="permissionThruDate" entity-alias="UGP" field="thruDate"/>
268 </view-entity>
269 */
270 }
271
272 Map<String, Object> getKeycloakUsers() { 467 Map<String, Object> getKeycloakUsers() {
273 String keycloakClientId = 'moqui'
274
275 KeycloakToolFactory keycloakToolFactory = ec.getFactory().getToolFactory('Keycloak')
276 Keycloak keycloak = keycloakToolFactory.getInstance()
277
278 try { 468 try {
279 RealmResource realm = keycloak.realm(keycloakToolFactory.getRealmName()) 469 updateUser('c6f99571-a79d-4267-b76e-a02a6847c8c9', '100000')
280 /*
281 ServerInfoResource serverInfo = keycloak.serverInfo()
282 logger.info('keycloak serverInfo: ' + keycloakToJson(serverInfo.getInfo()))
283 logger.info('keycloak keys: ' + keycloakToJson(realm.keys().getKeyMetadata()))
284 UsersResource users = realm.users()
285 for (UserRepresentation user: users.list()) {
286 logger.info('keycloak user: ' + keycloakToJson(user))
287 }
288 */
289 updateUser(realm, keycloakClientId, 'c6a4cb53-4533-4236-89e5-058967b9b90a', '100003')
290 logger.info("access token=${keycloakToJson(keycloak.tokenManager().getAccessToken())}")
291
292 } finally { 470 } finally {
293 keycloak.close() 471 cleanupKeycloak()
294 } 472 }
295 } 473 }
296 474
......
...@@ -14,6 +14,55 @@ along with this software (see the LICENSE.md file). If not, see ...@@ -14,6 +14,55 @@ along with this software (see the LICENSE.md file). If not, see
14 --> 14 -->
15 <services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-definition-3.xsd"> 15 <services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-definition-3.xsd">
16 16
17 <service verb="send" noun="UserAccount" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="syncUserAccount" authenticate="anonymous-all">
18 <in-parameters>
19 <parameter name="userId" type="String" required="true"/>
20 <parameter name="newPassword" required="false"/>
21 <parameter name="requirePasswordChange"/>
22 </in-parameters>
23 </service>
24
25 <service verb="update" noun="PasswordInternal" authenticate="false" allow-remote="false" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="updatePasswordInternal">
26 <in-parameters>
27 <parameter name="userId" required="true"/>
28 <parameter name="newPassword" required="true"/>
29 <parameter name="requirePasswordChange"/>
30 </in-parameters>
31 </service>
32
33 <service verb="send" noun="UserGroup" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="syncUserGroup" authenticate="anonymous-all">
34 <in-parameters>
35 <parameter name="userGroupId" type="String" required="true"/>
36 </in-parameters>
37 </service>
38
39 <service verb="send" noun="UserPermission" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="syncUserPermission" authenticate="anonymous-all">
40 <in-parameters>
41 <parameter name="userPermissionId" type="String" required="true"/>
42 </in-parameters>
43 </service>
44
45 <service verb="send" noun="RoleType" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="syncRoleType" authenticate="anonymous-all">
46 <in-parameters>
47 <parameter name="roleTypeId" type="String" required="true"/>
48 </in-parameters>
49 </service>
50
51 <service verb="send" noun="PartyClassification" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="syncPartyClassification" authenticate="anonymous-all">
52 <in-parameters>
53 <parameter name="partyClassificationId" type="String" required="true"/>
54 </in-parameters>
55 </service>
56
17 <service verb="get" noun="KeycloakUsers" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="getKeycloakUsers"> 57 <service verb="get" noun="KeycloakUsers" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="getKeycloakUsers">
18 </service> 58 </service>
59
60 <!--
61 <service verb="update" noun="PasswordInternal" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="updatePasswordInternal" authenticate="anonymous-all">
62 <in-parameters>
63 <parameter name="userId" type="String" required="true"/>
64 <parameter name="newPassword" type="String" required="true"/>
65 </in-parameters>
66 </service>
67 -->
19 </services> 68 </services>
......