d3562157 by Adam Heath

Checkpointing current work; can update client role mappings for a user.

1 parent 2065117c
...@@ -35,7 +35,8 @@ tasks.withType(GroovyCompile) { options.compilerArgs << "-proc:none" } ...@@ -35,7 +35,8 @@ tasks.withType(GroovyCompile) { options.compilerArgs << "-proc:none" }
35 35
36 dependencies { 36 dependencies {
37 implementation project(':framework') 37 implementation project(':framework')
38 implementation 'org.keycloak:keycloak-servlet-filter-adapter:15.0.2' 38 implementation 'org.keycloak:keycloak-servlet-filter-adapter:21.0.1'
39 implementation 'org.keycloak:keycloak-admin-client:21.0.1'
39 } 40 }
40 41
41 task cleanLib(type: Delete) { delete fileTree(dir: projectDir.absolutePath+'/lib', include: '*') } 42 task cleanLib(type: Delete) { delete fileTree(dir: projectDir.absolutePath+'/lib', include: '*') }
......
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 <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
16 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/entity-definition-3.xsd">
17
18 <extend-entity entity-name="UserAccount" package="moqui.security">
19 <index name="USERACCOUNT_EXTERNAL_USER" unique="true"><index-field name="externalUserId"/></index>
20 </extend-entity>
21 </entities>
1 import groovy.json.JsonOutput
2 import groovy.transform.Field
3
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
13 import org.slf4j.Logger
14 import org.slf4j.LoggerFactory
15
16 import groovy.json.JsonSlurper
17
18 import org.moqui.util.RestClient
19 import org.moqui.util.ObjectUtilities
20 import org.moqui.context.ExecutionContext
21 import org.moqui.entity.EntityCondition
22 import org.moqui.entity.EntityFind
23 import org.moqui.entity.EntityList
24 import org.moqui.entity.EntityValue
25
26 import org.keycloak.OAuth2Constants
27 import org.keycloak.admin.client.Keycloak
28 import org.keycloak.admin.client.KeycloakBuilder
29 import org.keycloak.admin.client.resource.ClientResource
30 import org.keycloak.admin.client.resource.RealmResource
31 import org.keycloak.admin.client.resource.RoleMappingResource
32 import org.keycloak.admin.client.resource.RoleScopeResource
33 import org.keycloak.admin.client.resource.RolesResource
34 import org.keycloak.admin.client.resource.ServerInfoResource
35 import org.keycloak.admin.client.resource.UserResource
36 import org.keycloak.admin.client.resource.UsersResource
37 import org.keycloak.representations.idm.ClientMappingsRepresentation
38 import org.keycloak.representations.idm.MappingsRepresentation
39 import org.keycloak.representations.idm.RoleRepresentation
40 import org.keycloak.representations.idm.UserRepresentation
41 import org.keycloak.util.JsonSerialization
42
43 @Field Logger logger = LoggerFactory.getLogger('KeycloakServices')
44
45 Long timestampToKeycloak(Timestamp timestamp) {
46 return timestamp.getTime()
47 }
48
49 String keycloakToJson(Object o) {
50 return JsonSerialization.writeValueAsString(o)
51 }
52
53 Keycloak buildKeycloak() {
54 return KeycloakBuilder.builder()
55 .serverUrl('http://keycloak')
56 .realm('master')
57 .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
58 .clientId('moqui')
59 .clientSecret('iXsnjGEbIVT8DQky2yCU9NQhnqDYyi7g')
60 .build()
61 }
62
63 Map<String, Object> buildClientConsent() {
64 return [
65 clientId: null,
66 createdDate: null, // int64
67 grantedClientScopes: [], // array[string]
68 lastUpdatedData: null, // int64
69 ]
70 }
71
72 Map<String, Object> buildCredential() {
73 return [
74 createdDate: null, // int64
75 credentialData: null, // string
76 id: null, // string
77 priority: null, // int32
78 secretData: null, // string
79 temporary: null, // boolean
80 type: null, // string
81 userLabel: null, // string
82 value: null, // string
83 ]
84 }
85
86 Map<String, Object> buildFederatedIdentiy() {
87 return [
88 identityProvider: null, // string
89 userId: null, // string
90 userName: null, // string
91 ]
92 }
93
94 Map<String, Object> buildKeycloakUser(String userId) {
95 EntityValue userLogin = ec.entity.find('UserLogin').condition('userId', userId)
96
97 Map<String, Object> keycloakUser = [
98 access: [:],
99 attributes: [:],
100 clientConsents: [],
101 clientRoles: [:],
102 createdTimestamp: null, // int64
103 credentials: [],
104 disableableCredentialTypes: [], // array[string]
105 email: null, // string
106 emailVerified: null, // boolean
107 enabled: null, // boolean
108 federatedIdentities: [],
109 federationLink: null, // string
110 firstName: null,
111 groups: [], // array[string]
112 id: null, // string
113 lastName: null,
114 notBefore: null, // int32
115 origin: null, // string
116 realmRoles: [], // array[string]
117 requiredActions: [], // array[string]
118 self: null, // string
119 serviceClientId: null, // string
120 username: null, // string
121 ]
122
123 return keycloakUser
124 }
125
126 Map<String, Object> onUpdateEmailAddress() {
127
128
129 }
130
131 Map<String, Object> joinUserAccountToKeycloak() {
132 String userId = context.userId
133
134 String externalUserId = context.externalUserId
135 if (externalUserId && externalUserId.startsWith('keycloak:')) {
136 // Already joined
137 return [:]
138 }
139
140 Keycloak keycloak = buildKeycloak()
141
142 try {
143 RealmResource realm = keycloak.realm('master')
144 UsersResource users = realm.users()
145 for (UserRepresentation user: users.list()) {
146 logger.info('keycloak user: ' + keycloakToJson(user))
147 }
148 } finally {
149 keycloak.close()
150 }
151
152 }
153
154 Map<String, Object> onUpdateUserAccount() {
155 String userId = context.userId
156 String externalUserId = context.externalUserId
157 if (!(externalUserId && externalUserId.startsWith('keycloak:'))) {
158 return [:]
159 }
160 String keycloakUserId = externalUserId.substring('keycloak:'.length())
161
162
163 }
164
165 Map<String, RoleRepresentation> getClientRoles(RealmResource realm, ClientResource clientResource, Collection<String> roleList) {
166 //logger.info('keycloak client: ' + keycloakToJson(clientResource))
167
168 logger.info("roleList(${roleList.size()})=${roleList}")
169 Map<String, RoleRepresentation> result = [:]
170
171 for (String role: roleList) {
172 result[role] = null
173 }
174 RolesResource rolesResource = clientResource.roles()
175 for (RoleRepresentation roleRep: rolesResource.list()) {
176 String name = roleRep.getName()
177 if (result.containsKey(name)) {
178 result[name] = roleRep
179 }
180 }
181
182 for (Map.Entry<String, RoleRepresentation> resultEntry: result.entrySet()) {
183 logger.info("got role mapping: (${resultEntry.getKey()})=(${resultEntry.getValue()})")
184 }
185 for (Map.Entry<String, RoleRepresentation> resultEntry: result.entrySet()) {
186 String name = resultEntry.getKey()
187 if (resultEntry.getValue() == null) {
188 rolesResource.create(new RoleRepresentation(name, "", false))
189 resultEntry.setValue(rolesResource.get(name))
190 }
191 }
192
193 return result
194 }
195
196
197 void updateUser(RealmResource realm, String keycloakClientId, String keycloakUserId, String userId) {
198 String clientId = realm.clients().findByClientId(keycloakClientId).get(0).getId()
199 logger.info("keycloakClientId=${keycloakClientId} clientId=${clientId}")
200 ClientResource clientResource = realm.clients().get(clientId)
201
202 UsersResource usersResource = realm.users()
203 UserResource userResource = usersResource.get(keycloakUserId)
204
205 Timestamp now = ec.user.nowTimestamp
206
207 List<EntityValue> userPermissionList = ec.entity.find('UserPermissionCheck')
208 .condition('userId', userId)
209 .useCache(true)
210 .disableAuthz()
211 .list()
212 .filterByDate('groupFromDate', 'groupThruDate', now)
213 .filterByDate('permissionFromDate', 'permissionThruDate', now)
214 List<String> moquiPermissions = userPermisionList*.userPermissionId.collect { permission -> 'permission:' + permission }
215 List<String> moquiGroups = ec.user.userGroupIdSet.collect { group -> 'group:' + group }
216
217 Map<String, RoleRepresentation> wantedClientRoles = getClientRoles(realm, clientResource, moquiPermissions + moquiGroups)
218
219 UserRepresentation userRep = userResource.toRepresentation()
220 Map<String, List<String>> attributes = new HashMap(userRep.getAttributes() ?: [:])
221
222 attributes['moqui:now'] = [now.toString()]
223
224
225 logger.info("user[$userId]} attributes: " + attributes)
226 userRep.setAttributes(attributes)
227
228 RoleMappingResource roleMappingResource = userResource.roles()
229 ClientMappingsRepresentation clientMappingsRespresentation = roleMappingResource.getAll().getClientMappings()[keycloakClientId]
230 for (RoleRepresentation existingRoleRep: clientMappingsRespresentation.getMappings()) {
231 if (!wantedClientRoles.remove(existingRoleRep.getName())) {
232 toRemove.add(existingRoleRep)
233 }
234 }
235 RoleScopeResource clientRoleScopeResource = roleMappingResource.clientLevel(clientId)
236 clientRoleScopeResource.remove(toRemove)
237 clientRoleScopeResource.add(wantedClientRoles.values() as List)
238
239 userResource.update(userRep)
240 }
241
242
243 Map<String, Object> pushKeycloakUser() {
244 String userId = context.userId
245 EntityValue userLogin = ec.entity.find('UserLogin').condition('userId', userId).one()
246
247
248 List<EntityValue> logins
249 return [:]
250
251 /*
252 <view-entity entity-name="UserPermissionCheck" package="moqui.security">
253 <member-entity entity-alias="UGM" entity-name="moqui.security.UserGroupMember"/>
254 <member-relationship entity-alias="UGP" join-from-alias="UGM" relationship="permissions"/>
255 <alias name="userGroupId" entity-alias="UGM"/>
256 <alias name="userId" entity-alias="UGM"/>
257 <alias name="userPermissionId" entity-alias="UGP"/>
258 <alias name="groupFromDate" entity-alias="UGM" field="fromDate"/>
259 <alias name="groupThruDate" entity-alias="UGM" field="thruDate"/>
260 <alias name="permissionFromDate" entity-alias="UGP" field="fromDate"/>
261 <alias name="permissionThruDate" entity-alias="UGP" field="thruDate"/>
262 </view-entity>
263 */
264 }
265
266 Map<String, Object> getKeycloakUsers() {
267 String keycloakClientId = 'moqui'
268
269 Keycloak keycloak = buildKeycloak()
270
271 try {
272 RealmResource realm = keycloak.realm('master')
273 /*
274 ServerInfoResource serverInfo = keycloak.serverInfo()
275 logger.info('keycloak serverInfo: ' + keycloakToJson(serverInfo.getInfo()))
276 logger.info('keycloak keys: ' + keycloakToJson(realm.keys().getKeyMetadata()))
277 UsersResource users = realm.users()
278 for (UserRepresentation user: users.list()) {
279 logger.info('keycloak user: ' + keycloakToJson(user))
280 }
281 */
282 updateUser(realm, keycloakClientId, 'c6a4cb53-4533-4236-89e5-058967b9b90a', '100000')
283
284 } finally {
285 keycloak.close()
286 }
287 }
288
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="get" noun="KeycloakUsers" type="script" location="component://moqui-keycloak/service/keycloak/KeycloakServices.groovy" method="getKeycloakUsers">
18 </service>
19 </services>