{"record":{"id":"ceec383d828bf774","repo":"apereo/cas","slug":"impersonation-is-not-allowed-for","errorCode":null,"errorMessage":"Impersonation is not allowed for [{}]","messagePattern":"Impersonation is not allowed for \\[(.+?)\\]","errorType":"exception","errorClass":"SurrogateAuthenticationException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/ticket/SurrogateServiceTicketGeneratorAuthority.java","lineNumber":61,"sourceCode":"        val authentication = authenticationResult.getAuthentication();\n        LOGGER.debug(\"Checking if service ticket generation is allowed for [{}] and [{}]\", authentication, service);\n        return findSurrogateCredentialTrait(authentication).isPresent();\n    }\n\n    @Override\n    public boolean shouldGenerate(final AuthenticationResult authenticationResult, final Service service) throws Throwable {\n        val authentication = authenticationResult.getAuthentication();\n        val result = findSurrogateCredentialTrait(authentication);\n        if (result.isPresent()) {\n            val pair = result.get();\n            val givenService = serviceSelectionPlan.resolveService(service);\n            val principal = resolvedPrincipal(pair.getKey().getId());\n            val surrogateUser = pair.getRight().getSurrogateUsername();\n            LOGGER.debug(\"Checking if [{}] can impersonate [{}] for service [{}]\", principal, surrogateUser, givenService);\n            if (surrogateAuthenticationService.canImpersonate(surrogateUser, principal, Optional.ofNullable(givenService))) {\n                return true;\n            }\n            LOGGER.warn(\"Impersonation is not allowed for [{}]\", surrogateUser);\n            throw new SurrogateAuthenticationException(\"Impersonating %s is not allowed\".formatted(surrogateUser));\n        }\n        return true;\n    }\n\n    protected Optional<Pair<Credential, SurrogateCredentialTrait>> findSurrogateCredentialTrait(\n        final Authentication authentication) {\n        return authentication.getCredentials()\n            .stream()\n            .filter(Objects::nonNull)\n            .filter(credential -> Objects.nonNull(credential.getCredentialMetadata()))\n            .filter(credential -> credential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class).isPresent())\n            .map(credential -> {\n                val credentialTrait = credential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class).orElseThrow();\n                return Pair.of(credential, credentialTrait);\n            })\n            .findFirst();\n    }","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/ticket/SurrogateServiceTicketGeneratorAuthority.java#L43-L79","documentation":"SurrogateServiceTicketGeneratorAuthority.shouldGenerate validates, before issuing a service ticket, that any surrogate credential trait present is legitimately allowed: it asks surrogateAuthenticationService.canImpersonate for the resolved principal and surrogate username. If impersonation is denied, it logs this warning and throws SurrogateAuthenticationException, refusing ticket generation. This is a hard authorization failure, unlike the earlier warn-and-return paths.","triggerScenarios":"A credential carrying SurrogateCredentialTrait(surrogateUser) reaches shouldGenerate, and surrogateAuthenticationService.canImpersonate(surrogateUser, principal, service) returns false (e.g. eligibility removed after authentication, or simple map lacks the principal).","commonSituations":"User authenticated before their eligibility was revoked, then requests a service ticket; mismatch between the surrogate service backing the webflow selection and the one validating ticket generation; principal id normalization differences causing canImpersonate to see a different id.","solutions":["Re-authenticate so eligibility is re-evaluated with current settings/LDAP state.","Restore the principal's eligibility (eligible-accounts map or LDAP member attribute) if denial is unintended.","Ensure the same surrogate authentication service implementation is used for selection and ticket generation.","Log principal.id and surrogateUser at debug to find normalization/casing mismatches."],"exampleFix":"// before\n// principal 'jdoe' not in eligible-accounts, ticket generation proceeds and throws\n// after\ncas.authn.surrogate.simple.eligible-accounts.jdoe=user1  # then re-login as jdoe","handlingStrategy":"try-catch","validationCode":"if (!surrogateAuthenticationService.canImpersonate(surrogateUser, principal, service)) {\n    throw new SurrogateAuthenticationException(\"Not allowed\"); // fail fast, don't request ticket\n}","typeGuard":"static boolean isSurrogateCredential(Credential c) {\n    return c.getCredentialMetadata() != null\n        && c.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class).isPresent();\n}","tryCatchPattern":"try {\n    ticket = ticketRegistryGrantor.grantServiceTicket(...);\n} catch (SurrogateAuthenticationException e) {\n    // re-authenticate or deny the impersonation request\n}","preventionTips":["Re-authenticate after eligibility changes","Use one surrogate service implementation everywhere","Monitor SurrogateAuthenticationException occurrences"],"tags":["surrogate","impersonation","authorization","service-ticket"],"backgroundTag":"permission-denied","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}