KeycloakServices.groovy 9.95 KB
import groovy.json.JsonOutput
import groovy.transform.Field

import java.sql.Timestamp
import java.util.Base64
import java.util.UUID
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.slf4j.Logger
import org.slf4j.LoggerFactory

import groovy.json.JsonSlurper

import org.moqui.util.RestClient
import org.moqui.util.ObjectUtilities
import org.moqui.context.ExecutionContext
import org.moqui.entity.EntityCondition
import org.moqui.entity.EntityFind
import org.moqui.entity.EntityList
import org.moqui.entity.EntityValue

import org.moqui.keycloak.KeycloakToolFactory


import org.keycloak.OAuth2Constants
import org.keycloak.admin.client.Keycloak
import org.keycloak.admin.client.KeycloakBuilder
import org.keycloak.admin.client.resource.ClientResource
import org.keycloak.admin.client.resource.RealmResource
import org.keycloak.admin.client.resource.RoleMappingResource
import org.keycloak.admin.client.resource.RoleScopeResource
import org.keycloak.admin.client.resource.RolesResource
import org.keycloak.admin.client.resource.ServerInfoResource
import org.keycloak.admin.client.resource.UserResource
import org.keycloak.admin.client.resource.UsersResource
import org.keycloak.representations.idm.ClientMappingsRepresentation
import org.keycloak.representations.idm.MappingsRepresentation
import org.keycloak.representations.idm.RoleRepresentation
import org.keycloak.representations.idm.UserRepresentation
import org.keycloak.util.JsonSerialization

@Field Logger logger = LoggerFactory.getLogger('KeycloakServices')

Long timestampToKeycloak(Timestamp timestamp) {
    return timestamp.getTime()
}

String keycloakToJson(Object o) {
    return JsonSerialization.writeValueAsString(o)
}

Map<String, Object> buildClientConsent() {
    return [
        clientId: null,
        createdDate: null, // int64
        grantedClientScopes: [], // array[string]
        lastUpdatedData: null, // int64
    ]
}

Map<String, Object> buildCredential() {
    return [
        createdDate: null, // int64
        credentialData: null, // string
        id: null, // string
        priority: null, // int32
        secretData: null, // string
        temporary: null, // boolean
        type: null, // string
        userLabel: null, // string
        value: null, // string
    ]
}

Map<String, Object> buildFederatedIdentiy() {
    return [
        identityProvider: null, // string
        userId: null, // string
        userName: null, // string
    ]
}

Map<String, Object> buildKeycloakUser(String userId) {
    EntityValue userLogin = ec.entity.find('UserLogin').condition('userId', userId)

    Map<String, Object> keycloakUser = [
        access: [:],
        attributes: [:],
        clientConsents: [],
        clientRoles: [:],
        createdTimestamp: null, // int64
        credentials: [],
        disableableCredentialTypes: [], // array[string]
        email: null, // string
        emailVerified: null, // boolean
        enabled: null, // boolean
        federatedIdentities: [], 
        federationLink: null, // string
        firstName: null,
        groups: [], // array[string]
        id: null, // string
        lastName: null,
        notBefore: null, // int32
        origin: null, // string
        realmRoles: [], // array[string]
        requiredActions: [], // array[string]
        self: null, // string
        serviceClientId: null, // string
        username: null, // string
    ]

    return keycloakUser
}

Map<String, Object> onUpdateEmailAddress() {


}

Map<String, Object> joinUserAccountToKeycloak() {
    String userId = context.userId

    String externalUserId = context.externalUserId
    if (externalUserId && externalUserId.startsWith('keycloak:')) {
        // Already joined
        return [:]
    }

    Keycloak keycloak = KeycloakToolFactory.getInstance()

    try {
        RealmResource realm = keycloak.realm('master')
        UsersResource users = realm.users()
        for (UserRepresentation user: users.list()) {
            logger.info('keycloak user: ' + keycloakToJson(user))
        }
    } finally {
        keycloak.close()
    }

}

Map<String, Object> onUpdateUserAccount() {
    String userId = context.userId
    String externalUserId = context.externalUserId
    if (!(externalUserId && externalUserId.startsWith('keycloak:'))) {
        return [:]
    }
    String keycloakUserId = externalUserId.substring('keycloak:'.length())


}

Map<String, RoleRepresentation> getClientRoles(RealmResource realm, ClientResource clientResource, Collection<String> roleList) {
    //logger.info('keycloak client: ' + keycloakToJson(clientResource))

    logger.info("roleList(${roleList.size()})=${roleList}")
    Map<String, RoleRepresentation> result = [:]

    for (String role: roleList) {
        result[role] = null
    }
    RolesResource rolesResource = clientResource.roles()
    for (RoleRepresentation roleRep: rolesResource.list()) {
        String name = roleRep.getName()
        if (result.containsKey(name)) {
            result[name] = roleRep
        }
    }

    for (Map.Entry<String, RoleRepresentation> resultEntry: result.entrySet()) {
        logger.info("got role mapping: (${resultEntry.getKey()})=(${resultEntry.getValue()})")
    }
    for (Map.Entry<String, RoleRepresentation> resultEntry: result.entrySet()) {
        String name = resultEntry.getKey()
        if (resultEntry.getValue() == null) {
            rolesResource.create(new RoleRepresentation(name, "", false))
            resultEntry.setValue(rolesResource.get(name))
        }
    }

    return result
}


void updateUser(RealmResource realm, String keycloakClientId, String keycloakUserId, String userId) {
    String clientId = realm.clients().findByClientId(keycloakClientId).get(0).getId()
    logger.info("keycloakClientId=${keycloakClientId} clientId=${clientId}")
    ClientResource clientResource = realm.clients().get(clientId)

    UsersResource usersResource = realm.users()
    UserResource userResource = usersResource.get(keycloakUserId)

    Timestamp now = ec.user.nowTimestamp

    List<EntityValue> userPermissionList = ec.entity.find('UserPermissionCheck')
        .condition('userId', userId)
        .useCache(false)
        .disableAuthz()
        .list()
        .filterByDate('groupFromDate', 'groupThruDate', now)
        .filterByDate('permissionFromDate', 'permissionThruDate', now)
    List<String> moquiPermissions = userPermisionList*.userPermissionId.collect { permission -> 'permission:' + permission }
    List<EntityValue> userGroupList = ec.entity.find('UserGroupMemberUser')
        .condition('userId', userId)
        .useCache(false)
        .disableAuthz()
        .list()
        .filterByDate('fromDate', 'thruDate', now)
    Set<String> moquiGroups = userGroupList*.userGroupId.collect { group -> 'group:' + group }
    moquiGroups.add('group:ALL_USERS')

    Map<String, RoleRepresentation> wantedClientRoles = getClientRoles(realm, clientResource, moquiPermissions + moquiGroups)

    UserRepresentation userRep = userResource.toRepresentation()
    Map<String, List<String>> attributes = new HashMap(userRep.getAttributes() ?: [:])

    attributes['moqui:now'] = [now.toString()]


    logger.info("user[$userId]} attributes: " + attributes)
    userRep.setAttributes(attributes)

    List<RoleRepresentation> toRemove = []
    RoleMappingResource roleMappingResource = userResource.roles()
    ClientMappingsRepresentation clientMappingsRespresentation = roleMappingResource.getAll().getClientMappings()?[keycloakClientId]
    for (RoleRepresentation existingRoleRep: clientMappingsRespresentation?.getMappings()) {
        if (!wantedClientRoles.remove(existingRoleRep.getName())) {
            toRemove.add(existingRoleRep)
        }
    }
    List<RoleRepresentation> toAdd = wantedClientRoles.values() as List
    logger.info("roles to remove: ${toRemove}")
    logger.info("roles to add: ${toAdd}")
    RoleScopeResource clientRoleScopeResource = roleMappingResource.clientLevel(clientId)
    clientRoleScopeResource.remove(toRemove)
    clientRoleScopeResource.add(toAdd)

    userResource.update(userRep)
    Map<String, Object> foo = userResource.impersonate()
    logger.info("impersonate: foo=${foo}")
}


Map<String, Object> pushKeycloakUser() {
    String userId = context.userId
    EntityValue userLogin = ec.entity.find('UserLogin').condition('userId', userId).one()


    List<EntityValue> logins
    return [:]

/*
    <view-entity entity-name="UserPermissionCheck" package="moqui.security">
        <member-entity entity-alias="UGM" entity-name="moqui.security.UserGroupMember"/>
        <member-relationship entity-alias="UGP" join-from-alias="UGM" relationship="permissions"/>
        <alias name="userGroupId" entity-alias="UGM"/>
        <alias name="userId" entity-alias="UGM"/>
        <alias name="userPermissionId" entity-alias="UGP"/>
        <alias name="groupFromDate" entity-alias="UGM" field="fromDate"/>
        <alias name="groupThruDate" entity-alias="UGM" field="thruDate"/>
        <alias name="permissionFromDate" entity-alias="UGP" field="fromDate"/>
        <alias name="permissionThruDate" entity-alias="UGP" field="thruDate"/>
    </view-entity>
*/
}

Map<String, Object> getKeycloakUsers() {
    String keycloakClientId = 'moqui'

    Keycloak keycloak = ec.getTool('Keycloak', Keycloak.class)

    try {
        RealmResource realm = keycloak.realm('master')
/*
        ServerInfoResource serverInfo = keycloak.serverInfo()
        logger.info('keycloak serverInfo: ' + keycloakToJson(serverInfo.getInfo()))
        logger.info('keycloak keys: ' + keycloakToJson(realm.keys().getKeyMetadata()))
        UsersResource users = realm.users()
        for (UserRepresentation user: users.list()) {
            logger.info('keycloak user: ' + keycloakToJson(user))
        }
*/
        updateUser(realm, keycloakClientId, 'c6a4cb53-4533-4236-89e5-058967b9b90a', '100003')
        logger.info("access token=${keycloakToJson(keycloak.tokenManager().getAccessToken())}")

    } finally {
        keycloak.close()
    }
}