Add protocol SPIs, endpoints for login/logout/serviceValidate and some
claim mapper stubs
New file |
| | |
| | | <?xml version="1.0"?> |
| | | <!-- |
| | | ~ Copyright 2016 Red Hat, Inc. and/or its affiliates |
| | | ~ and other contributors as indicated by the @author tags. |
| | | ~ |
| | | ~ Licensed under the Apache License, Version 2.0 (the "License"); |
| | | ~ you may not use this file except in compliance with the License. |
| | | ~ You may obtain a copy of the License at |
| | | ~ |
| | | ~ http://www.apache.org/licenses/LICENSE-2.0 |
| | | ~ |
| | | ~ Unless required by applicable law or agreed to in writing, software |
| | | ~ distributed under the License is distributed on an "AS IS" BASIS, |
| | | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | ~ See the License for the specific language governing permissions and |
| | | ~ limitations under the License. |
| | | --> |
| | | |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | |
| | | <groupId>org.keycloak</groupId> |
| | | <artifactId>keycloak-protocol-cas</artifactId> |
| | | <version>1.0.0-SNAPSHOT</version> |
| | | <name>Keycloak CAS Protocol</name> |
| | | <description /> |
| | | |
| | | <properties> |
| | | <keycloak.version>2.5.1.Final</keycloak.version> |
| | | <jboss.logging.version>3.3.0.Final</jboss.logging.version> |
| | | <jboss.logging.tools.version>2.0.1.Final</jboss.logging.tools.version> |
| | | <junit.version>4.12</junit.version> |
| | | |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | | <maven.compiler.target>1.8</maven.compiler.target> |
| | | <maven.compiler.source>1.8</maven.compiler.source> |
| | | </properties> |
| | | |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>org.keycloak</groupId> |
| | | <artifactId>keycloak-core</artifactId> |
| | | <version>${keycloak.version}</version> |
| | | <scope>provided</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.keycloak</groupId> |
| | | <artifactId>keycloak-server-spi</artifactId> |
| | | <version>${keycloak.version}</version> |
| | | <scope>provided</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.keycloak</groupId> |
| | | <artifactId>keycloak-server-spi-private</artifactId> |
| | | <version>${keycloak.version}</version> |
| | | <scope>provided</scope> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.jboss.logging</groupId> |
| | | <artifactId>jboss-logging</artifactId> |
| | | <version>${jboss.logging.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.jboss.logging</groupId> |
| | | <artifactId>jboss-logging-annotations</artifactId> |
| | | <version>${jboss.logging.tools.version}</version> |
| | | <scope>provided</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.jboss.logging</groupId> |
| | | <artifactId>jboss-logging-processor</artifactId> |
| | | <version>${jboss.logging.tools.version}</version> |
| | | <scope>provided</scope> |
| | | <optional>true</optional> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.keycloak</groupId> |
| | | <artifactId>keycloak-services</artifactId> |
| | | <version>${keycloak.version}</version> |
| | | <scope>provided</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>junit</groupId> |
| | | <artifactId>junit</artifactId> |
| | | <version>${junit.version}</version> |
| | | <scope>test</scope> |
| | | </dependency> |
| | | </dependencies> |
| | | <build> |
| | | <plugins> |
| | | <plugin> |
| | | <groupId>org.apache.maven.plugins</groupId> |
| | | <artifactId>maven-compiler-plugin</artifactId> |
| | | <version>3.1</version> |
| | | <configuration> |
| | | <source>${maven.compiler.source}</source> |
| | | <target>${maven.compiler.target}</target> |
| | | <compilerArgument> |
| | | -AgeneratedTranslationFilesPath=${project.build.directory}/generated-translation-files |
| | | </compilerArgument> |
| | | </configuration> |
| | | </plugin> |
| | | </plugins> |
| | | </build> |
| | | </project> |
New file |
| | |
| | | package org.keycloak.protocol.cas; |
| | | |
| | | import org.keycloak.common.util.KeycloakUriBuilder; |
| | | import org.keycloak.events.EventBuilder; |
| | | import org.keycloak.events.EventType; |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.protocol.LoginProtocol; |
| | | import org.keycloak.services.managers.ClientSessionCode; |
| | | import org.keycloak.services.managers.ResourceAdminManager; |
| | | |
| | | import javax.ws.rs.core.HttpHeaders; |
| | | import javax.ws.rs.core.Response; |
| | | import javax.ws.rs.core.UriInfo; |
| | | import java.net.URI; |
| | | |
| | | public class CASLoginProtocol implements LoginProtocol { |
| | | public static final String LOGIN_PROTOCOL = "cas"; |
| | | |
| | | public static final String SERVICE_PARAM = "service"; |
| | | public static final String RENEW_PARAM = "renew"; |
| | | public static final String GATEWAY_PARAM = "gateway"; |
| | | public static final String TICKET_PARAM = "ticket"; |
| | | public static final String FORMAT_PARAM = "format"; |
| | | |
| | | public static final String TICKET_RESPONSE_PARAM = "ticket"; |
| | | |
| | | public static final String SERVICE_TICKET_PREFIX = "ST-"; |
| | | |
| | | protected KeycloakSession session; |
| | | protected RealmModel realm; |
| | | protected UriInfo uriInfo; |
| | | protected HttpHeaders headers; |
| | | protected EventBuilder event; |
| | | private boolean requireReauth; |
| | | |
| | | public CASLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event, boolean requireReauth) { |
| | | this.session = session; |
| | | this.realm = realm; |
| | | this.uriInfo = uriInfo; |
| | | this.headers = headers; |
| | | this.event = event; |
| | | this.requireReauth = requireReauth; |
| | | } |
| | | |
| | | public CASLoginProtocol() { |
| | | } |
| | | |
| | | @Override |
| | | public CASLoginProtocol setSession(KeycloakSession session) { |
| | | this.session = session; |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | public CASLoginProtocol setRealm(RealmModel realm) { |
| | | this.realm = realm; |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | public CASLoginProtocol setUriInfo(UriInfo uriInfo) { |
| | | this.uriInfo = uriInfo; |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | public CASLoginProtocol setHttpHeaders(HttpHeaders headers) { |
| | | this.headers = headers; |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | public CASLoginProtocol setEventBuilder(EventBuilder event) { |
| | | this.event = event; |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) { |
| | | ClientSessionModel clientSession = accessCode.getClientSession(); |
| | | |
| | | String service = clientSession.getRedirectUri(); |
| | | //TODO validate service |
| | | accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name()); |
| | | KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(service); |
| | | uriBuilder.queryParam(TICKET_RESPONSE_PARAM, SERVICE_TICKET_PREFIX + accessCode.getCode()); |
| | | |
| | | URI redirectUri = uriBuilder.build(); |
| | | |
| | | Response.ResponseBuilder location = Response.status(302).location(redirectUri); |
| | | return location.build(); |
| | | } |
| | | |
| | | @Override |
| | | public Response sendError(ClientSessionModel clientSession, Error error) { |
| | | return Response.serverError().entity(error).build(); |
| | | } |
| | | |
| | | @Override |
| | | public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) { |
| | | ClientModel client = clientSession.getClient(); |
| | | new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, client, clientSession); |
| | | } |
| | | |
| | | @Override |
| | | public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) { |
| | | // todo oidc redirect support |
| | | throw new RuntimeException("NOT IMPLEMENTED"); |
| | | } |
| | | |
| | | @Override |
| | | public Response finishLogout(UserSessionModel userSession) { |
| | | event.event(EventType.LOGOUT); |
| | | event.user(userSession.getUser()).session(userSession).success(); |
| | | return Response.ok().build(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean requireReauthentication(UserSessionModel userSession, ClientSessionModel clientSession) { |
| | | return requireReauth; |
| | | } |
| | | |
| | | @Override |
| | | public void close() { |
| | | |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas; |
| | | |
| | | import org.jboss.logging.Logger; |
| | | import org.keycloak.events.EventBuilder; |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.protocol.AbstractLoginProtocolFactory; |
| | | import org.keycloak.protocol.LoginProtocol; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | | import org.keycloak.protocol.cas.mappers.FullNameMapper; |
| | | import org.keycloak.protocol.cas.mappers.UserAttributeMapper; |
| | | import org.keycloak.protocol.cas.mappers.UserPropertyMapper; |
| | | import org.keycloak.representations.idm.ClientRepresentation; |
| | | import org.keycloak.representations.idm.ClientTemplateRepresentation; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.JSON_TYPE; |
| | | import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME; |
| | | |
| | | public class CASLoginProtocolFactory extends AbstractLoginProtocolFactory { |
| | | private static final Logger logger = Logger.getLogger(CASLoginProtocolFactory.class); |
| | | |
| | | public static final String EMAIL = "email"; |
| | | public static final String EMAIL_VERIFIED = "email verified"; |
| | | public static final String GIVEN_NAME = "given name"; |
| | | public static final String FAMILY_NAME = "family name"; |
| | | public static final String FULL_NAME = "full name"; |
| | | public static final String LOCALE = "locale"; |
| | | |
| | | public static final String EMAIL_CONSENT_TEXT = "${email}"; |
| | | public static final String EMAIL_VERIFIED_CONSENT_TEXT = "${emailVerified}"; |
| | | public static final String GIVEN_NAME_CONSENT_TEXT = "${givenName}"; |
| | | public static final String FAMILY_NAME_CONSENT_TEXT = "${familyName}"; |
| | | public static final String FULL_NAME_CONSENT_TEXT = "${fullName}"; |
| | | public static final String LOCALE_CONSENT_TEXT = "${locale}"; |
| | | |
| | | @Override |
| | | public LoginProtocol create(KeycloakSession session) { |
| | | return new CASLoginProtocol().setSession(session); |
| | | } |
| | | |
| | | @Override |
| | | public List<ProtocolMapperModel> getBuiltinMappers() { |
| | | return builtins; |
| | | } |
| | | |
| | | @Override |
| | | public List<ProtocolMapperModel> getDefaultBuiltinMappers() { |
| | | return defaultBuiltins; |
| | | } |
| | | |
| | | static List<ProtocolMapperModel> builtins = new ArrayList<>(); |
| | | static List<ProtocolMapperModel> defaultBuiltins = new ArrayList<>(); |
| | | |
| | | static { |
| | | ProtocolMapperModel model; |
| | | |
| | | model = UserPropertyMapper.create(EMAIL, "email", "mail", "String", |
| | | true, EMAIL_CONSENT_TEXT); |
| | | builtins.add(model); |
| | | defaultBuiltins.add(model); |
| | | model = UserPropertyMapper.create(GIVEN_NAME, "firstName", "givenName", "String", |
| | | true, GIVEN_NAME_CONSENT_TEXT); |
| | | builtins.add(model); |
| | | defaultBuiltins.add(model); |
| | | model = UserPropertyMapper.create(FAMILY_NAME, "lastName", "sn", "String", |
| | | true, FAMILY_NAME_CONSENT_TEXT); |
| | | builtins.add(model); |
| | | defaultBuiltins.add(model); |
| | | model = UserPropertyMapper.create(EMAIL_VERIFIED, |
| | | "emailVerified", |
| | | "emailVerified", "boolean", |
| | | false, EMAIL_VERIFIED_CONSENT_TEXT); |
| | | builtins.add(model); |
| | | model = UserAttributeMapper.create(LOCALE, |
| | | "locale", |
| | | "locale", "String", |
| | | false, LOCALE_CONSENT_TEXT, |
| | | false); |
| | | builtins.add(model); |
| | | |
| | | model = FullNameMapper.create(FULL_NAME, "cn", |
| | | true, FULL_NAME_CONSENT_TEXT); |
| | | builtins.add(model); |
| | | defaultBuiltins.add(model); |
| | | } |
| | | |
| | | @Override |
| | | protected void addDefaults(ClientModel client) { |
| | | for (ProtocolMapperModel model : defaultBuiltins) client.addProtocolMapper(model); |
| | | } |
| | | |
| | | @Override |
| | | public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) { |
| | | return new CASLoginProtocolService(realm, event); |
| | | } |
| | | |
| | | @Override |
| | | public String getId() { |
| | | return CASLoginProtocol.LOGIN_PROTOCOL; |
| | | } |
| | | |
| | | @Override |
| | | public void setupClientDefaults(ClientRepresentation rep, ClientModel newClient) { |
| | | if (rep.getRootUrl() != null && (rep.getRedirectUris() == null || rep.getRedirectUris().isEmpty())) { |
| | | String root = rep.getRootUrl(); |
| | | if (root.endsWith("/")) root = root + "*"; |
| | | else root = root + "/*"; |
| | | newClient.addRedirectUri(root); |
| | | } |
| | | |
| | | if (rep.getAdminUrl() == null && rep.getRootUrl() != null) { |
| | | newClient.setManagementUrl(rep.getRootUrl()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void setupTemplateDefaults(ClientTemplateRepresentation clientRep, ClientTemplateModel newClient) { |
| | | |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas; |
| | | |
| | | import org.jboss.resteasy.spi.HttpRequest; |
| | | import org.jboss.resteasy.spi.ResteasyProviderFactory; |
| | | import org.keycloak.events.EventBuilder; |
| | | import org.keycloak.models.KeycloakSession; |
| | | import org.keycloak.models.RealmModel; |
| | | import org.keycloak.protocol.cas.endpoints.AuthorizationEndpoint; |
| | | import org.keycloak.protocol.cas.endpoints.LogoutEndpoint; |
| | | import org.keycloak.protocol.cas.endpoints.ServiceValidateEndpoint; |
| | | import org.keycloak.protocol.cas.endpoints.ValidateEndpoint; |
| | | import org.keycloak.services.resources.RealmsResource; |
| | | |
| | | import javax.ws.rs.Path; |
| | | import javax.ws.rs.core.Context; |
| | | import javax.ws.rs.core.HttpHeaders; |
| | | import javax.ws.rs.core.UriBuilder; |
| | | import javax.ws.rs.core.UriInfo; |
| | | |
| | | public class CASLoginProtocolService { |
| | | private RealmModel realm; |
| | | private EventBuilder event; |
| | | |
| | | @Context |
| | | private UriInfo uriInfo; |
| | | |
| | | @Context |
| | | private KeycloakSession session; |
| | | |
| | | @Context |
| | | private HttpHeaders headers; |
| | | |
| | | @Context |
| | | private HttpRequest request; |
| | | |
| | | public CASLoginProtocolService(RealmModel realm, EventBuilder event) { |
| | | this.realm = realm; |
| | | this.event = event; |
| | | } |
| | | |
| | | public static UriBuilder serviceBaseUrl(UriBuilder baseUriBuilder) { |
| | | return baseUriBuilder.path(RealmsResource.class).path("{realm}/protocol/" + CASLoginProtocol.LOGIN_PROTOCOL); |
| | | } |
| | | |
| | | @Path("login") |
| | | public Object login() { |
| | | AuthorizationEndpoint endpoint = new AuthorizationEndpoint(realm, event); |
| | | ResteasyProviderFactory.getInstance().injectProperties(endpoint); |
| | | return endpoint; |
| | | } |
| | | |
| | | @Path("logout") |
| | | public Object logout() { |
| | | LogoutEndpoint endpoint = new LogoutEndpoint(realm, event); |
| | | ResteasyProviderFactory.getInstance().injectProperties(endpoint); |
| | | return endpoint; |
| | | } |
| | | |
| | | @Path("validate") |
| | | public Object validate() { |
| | | ValidateEndpoint endpoint = new ValidateEndpoint(realm, event); |
| | | ResteasyProviderFactory.getInstance().injectProperties(endpoint); |
| | | return endpoint; |
| | | } |
| | | |
| | | @Path("serviceValidate") |
| | | public Object serviceValidate() { |
| | | ServiceValidateEndpoint endpoint = new ServiceValidateEndpoint(realm, event); |
| | | ResteasyProviderFactory.getInstance().injectProperties(endpoint); |
| | | return endpoint; |
| | | } |
| | | |
| | | @Path("proxyValidate") |
| | | public Object proxyValidate() { |
| | | return null; |
| | | } |
| | | |
| | | @Path("proxy") |
| | | public Object proxy() { |
| | | return null; |
| | | } |
| | | |
| | | @Path("p3/serviceValidate") |
| | | public Object p3ServiceValidate() { |
| | | return serviceValidate(); |
| | | } |
| | | |
| | | @Path("p3/proxyValidate") |
| | | public Object p3ProxyValidate() { |
| | | return proxyValidate(); |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.endpoints; |
| | | |
| | | import org.jboss.logging.Logger; |
| | | import org.keycloak.events.Details; |
| | | import org.keycloak.events.Errors; |
| | | import org.keycloak.events.EventBuilder; |
| | | import org.keycloak.events.EventType; |
| | | import org.keycloak.models.ClientModel; |
| | | import org.keycloak.models.ClientSessionModel; |
| | | import org.keycloak.models.RealmModel; |
| | | import org.keycloak.protocol.AuthorizationEndpointBase; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | import org.keycloak.protocol.oidc.utils.RedirectUtils; |
| | | import org.keycloak.services.ErrorPageException; |
| | | import org.keycloak.services.messages.Messages; |
| | | import org.keycloak.services.util.CacheControlUtil; |
| | | |
| | | import javax.ws.rs.GET; |
| | | import javax.ws.rs.core.MultivaluedMap; |
| | | import javax.ws.rs.core.Response; |
| | | |
| | | public class AuthorizationEndpoint extends AuthorizationEndpointBase { |
| | | private static final Logger logger = Logger.getLogger(AuthorizationEndpoint.class); |
| | | |
| | | private ClientModel client; |
| | | private ClientSessionModel clientSession; |
| | | private String redirectUri; |
| | | |
| | | public AuthorizationEndpoint(RealmModel realm, EventBuilder event) { |
| | | super(realm, event); |
| | | event.event(EventType.LOGIN); |
| | | } |
| | | |
| | | @GET |
| | | public Response build() { |
| | | MultivaluedMap<String, String> params = uriInfo.getQueryParameters(); |
| | | String service = params.getFirst(CASLoginProtocol.SERVICE_PARAM); |
| | | boolean renew = "true".equalsIgnoreCase(params.getFirst(CASLoginProtocol.RENEW_PARAM)); |
| | | boolean gateway = "true".equalsIgnoreCase(params.getFirst(CASLoginProtocol.GATEWAY_PARAM)); |
| | | |
| | | checkSsl(); |
| | | checkRealm(); |
| | | checkClient(service); |
| | | |
| | | createClientSession(); |
| | | // So back button doesn't work |
| | | CacheControlUtil.noBackButtonCacheControlHeader(); |
| | | |
| | | this.event.event(EventType.LOGIN); |
| | | return handleBrowserAuthenticationRequest(clientSession, new CASLoginProtocol(session, realm, uriInfo, headers, event, renew), gateway, false); |
| | | } |
| | | |
| | | private void checkSsl() { |
| | | if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) { |
| | | event.error(Errors.SSL_REQUIRED); |
| | | throw new ErrorPageException(session, Messages.HTTPS_REQUIRED); |
| | | } |
| | | } |
| | | |
| | | private void checkRealm() { |
| | | if (!realm.isEnabled()) { |
| | | event.error(Errors.REALM_DISABLED); |
| | | throw new ErrorPageException(session, Messages.REALM_NOT_ENABLED); |
| | | } |
| | | } |
| | | |
| | | private void checkClient(String service) { |
| | | if (service == null) { |
| | | event.error(Errors.INVALID_REQUEST); |
| | | throw new ErrorPageException(session, Messages.MISSING_PARAMETER, CASLoginProtocol.SERVICE_PARAM); |
| | | } |
| | | |
| | | client = realm.getClients().stream() |
| | | .filter(c -> CASLoginProtocol.LOGIN_PROTOCOL.equals(c.getProtocol())) |
| | | .filter(c -> RedirectUtils.verifyRedirectUri(uriInfo, service, realm, c) != null) |
| | | .findFirst().orElse(null); |
| | | if (client == null) { |
| | | event.error(Errors.CLIENT_NOT_FOUND); |
| | | throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND); |
| | | } |
| | | |
| | | if (!client.isEnabled()) { |
| | | event.error(Errors.CLIENT_DISABLED); |
| | | throw new ErrorPageException(session, Messages.CLIENT_DISABLED); |
| | | } |
| | | |
| | | if (client.isBearerOnly()) { |
| | | event.error(Errors.NOT_ALLOWED); |
| | | throw new ErrorPageException(session, Messages.BEARER_ONLY); |
| | | } |
| | | |
| | | redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, service, realm, client); |
| | | |
| | | event.client(client.getClientId()); |
| | | event.detail(Details.REDIRECT_URI, redirectUri); |
| | | |
| | | session.getContext().setClient(client); |
| | | } |
| | | |
| | | private void createClientSession() { |
| | | clientSession = session.sessions().createClientSession(realm, client); |
| | | clientSession.setAuthMethod(CASLoginProtocol.LOGIN_PROTOCOL); |
| | | clientSession.setRedirectUri(redirectUri); |
| | | clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.endpoints; |
| | | |
| | | import org.jboss.logging.Logger; |
| | | import org.jboss.resteasy.annotations.cache.NoCache; |
| | | import org.jboss.resteasy.spi.HttpRequest; |
| | | import org.keycloak.common.ClientConnection; |
| | | import org.keycloak.events.EventBuilder; |
| | | import org.keycloak.models.KeycloakSession; |
| | | import org.keycloak.models.RealmModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | import org.keycloak.services.managers.AuthenticationManager; |
| | | |
| | | import javax.ws.rs.GET; |
| | | import javax.ws.rs.core.Context; |
| | | import javax.ws.rs.core.HttpHeaders; |
| | | import javax.ws.rs.core.Response; |
| | | import javax.ws.rs.core.UriInfo; |
| | | |
| | | public class LogoutEndpoint { |
| | | private static final Logger logger = Logger.getLogger(org.keycloak.protocol.oidc.endpoints.LogoutEndpoint.class); |
| | | |
| | | @Context |
| | | private KeycloakSession session; |
| | | |
| | | @Context |
| | | private ClientConnection clientConnection; |
| | | |
| | | @Context |
| | | private HttpRequest request; |
| | | |
| | | @Context |
| | | private HttpHeaders headers; |
| | | |
| | | @Context |
| | | private UriInfo uriInfo; |
| | | |
| | | private RealmModel realm; |
| | | private EventBuilder event; |
| | | |
| | | public LogoutEndpoint(RealmModel realm, EventBuilder event) { |
| | | this.realm = realm; |
| | | this.event = event; |
| | | } |
| | | |
| | | @GET |
| | | @NoCache |
| | | public Response logout() { |
| | | |
| | | AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false); |
| | | if (authResult != null) { |
| | | UserSessionModel userSession = authResult.getSession(); |
| | | userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, CASLoginProtocol.LOGIN_PROTOCOL); |
| | | |
| | | logger.debug("Initiating CAS browser logout"); |
| | | Response response = AuthenticationManager.browserLogout(session, realm, authResult.getSession(), uriInfo, clientConnection, headers); |
| | | logger.debug("finishing CAS browser logout"); |
| | | return response; |
| | | } |
| | | return Response.ok().build(); |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.endpoints; |
| | | |
| | | import org.keycloak.events.EventBuilder; |
| | | import org.keycloak.models.KeycloakSessionFactory; |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.models.RealmModel; |
| | | import org.keycloak.models.UserSessionModel; |
| | | import org.keycloak.protocol.ProtocolMapper; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | import org.keycloak.services.ErrorResponseException; |
| | | import org.keycloak.services.managers.ClientSessionCode; |
| | | |
| | | import javax.ws.rs.core.HttpHeaders; |
| | | import javax.ws.rs.core.MediaType; |
| | | import javax.ws.rs.core.Response; |
| | | import java.util.Set; |
| | | |
| | | public class ServiceValidateEndpoint extends ValidateEndpoint { |
| | | public ServiceValidateEndpoint(RealmModel realm, EventBuilder event) { |
| | | super(realm, event); |
| | | } |
| | | |
| | | @Override |
| | | protected Response successResponse() { |
| | | UserSessionModel userSession = clientSession.getUserSession(); |
| | | |
| | | Set<ProtocolMapperModel> mappings = new ClientSessionCode(session, realm, clientSession).getRequestedProtocolMappers(); |
| | | KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); |
| | | for (ProtocolMapperModel mapping : mappings) { |
| | | ProtocolMapper mapper = (ProtocolMapper) sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper()); |
| | | } |
| | | |
| | | return Response.ok() |
| | | .header(HttpHeaders.CONTENT_TYPE, (jsonFormat() ? MediaType.APPLICATION_JSON_TYPE : MediaType.APPLICATION_XML_TYPE).withCharset("utf-8")) |
| | | .entity("<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n" + |
| | | " <cas:authenticationSuccess>\n" + |
| | | " <cas:user>" + userSession.getUser().getUsername() + "</cas:user>\n" + |
| | | " <cas:attributes>\n" + |
| | | " </cas:attributes>\n" + |
| | | " </cas:authenticationSuccess>\n" + |
| | | "</cas:serviceResponse>") |
| | | .build(); |
| | | } |
| | | |
| | | @Override |
| | | protected Response errorResponse(ErrorResponseException e) { |
| | | return super.errorResponse(e); |
| | | } |
| | | |
| | | private boolean jsonFormat() { |
| | | return "json".equalsIgnoreCase(uriInfo.getQueryParameters().getFirst(CASLoginProtocol.FORMAT_PARAM)); |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.endpoints; |
| | | |
| | | import org.jboss.logging.Logger; |
| | | import org.jboss.resteasy.annotations.cache.NoCache; |
| | | import org.jboss.resteasy.spi.HttpRequest; |
| | | import org.keycloak.OAuthErrorException; |
| | | import org.keycloak.common.ClientConnection; |
| | | import org.keycloak.events.Details; |
| | | import org.keycloak.events.Errors; |
| | | import org.keycloak.events.EventBuilder; |
| | | import org.keycloak.events.EventType; |
| | | import org.keycloak.models.*; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | import org.keycloak.protocol.oidc.utils.RedirectUtils; |
| | | import org.keycloak.services.ErrorPageException; |
| | | import org.keycloak.services.ErrorResponseException; |
| | | import org.keycloak.services.managers.AuthenticationManager; |
| | | import org.keycloak.services.managers.ClientSessionCode; |
| | | import org.keycloak.services.messages.Messages; |
| | | |
| | | import javax.ws.rs.GET; |
| | | import javax.ws.rs.core.*; |
| | | |
| | | public class ValidateEndpoint { |
| | | protected static final Logger logger = Logger.getLogger(org.keycloak.protocol.oidc.endpoints.LogoutEndpoint.class); |
| | | |
| | | private static final String RESPONSE_OK = "yes\n"; |
| | | private static final String RESPONSE_FAILED = "no\n"; |
| | | |
| | | @Context |
| | | protected KeycloakSession session; |
| | | |
| | | @Context |
| | | protected ClientConnection clientConnection; |
| | | |
| | | @Context |
| | | protected HttpRequest request; |
| | | |
| | | @Context |
| | | protected HttpHeaders headers; |
| | | |
| | | @Context |
| | | protected UriInfo uriInfo; |
| | | |
| | | protected RealmModel realm; |
| | | protected EventBuilder event; |
| | | protected ClientModel client; |
| | | protected ClientSessionModel clientSession; |
| | | |
| | | public ValidateEndpoint(RealmModel realm, EventBuilder event) { |
| | | this.realm = realm; |
| | | this.event = event; |
| | | } |
| | | |
| | | @GET |
| | | @NoCache |
| | | public Response build() { |
| | | MultivaluedMap<String, String> params = uriInfo.getQueryParameters(); |
| | | String service = params.getFirst(CASLoginProtocol.SERVICE_PARAM); |
| | | String ticket = params.getFirst(CASLoginProtocol.TICKET_PARAM); |
| | | boolean renew = "true".equalsIgnoreCase(params.getFirst(CASLoginProtocol.RENEW_PARAM)); |
| | | |
| | | event.event(EventType.CODE_TO_TOKEN); |
| | | |
| | | try { |
| | | checkSsl(); |
| | | checkRealm(); |
| | | checkClient(service); |
| | | |
| | | checkTicket(ticket, renew); |
| | | |
| | | event.success(); |
| | | return successResponse(); |
| | | } catch (ErrorResponseException e) { |
| | | return errorResponse(e); |
| | | } |
| | | } |
| | | |
| | | protected Response successResponse() { |
| | | return Response.ok(RESPONSE_OK).type(MediaType.TEXT_PLAIN).build(); |
| | | } |
| | | |
| | | protected Response errorResponse(ErrorResponseException e) { |
| | | return Response.status(Response.Status.UNAUTHORIZED).entity(RESPONSE_FAILED).type(MediaType.TEXT_PLAIN).build(); |
| | | } |
| | | |
| | | private void checkSsl() { |
| | | if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) { |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "HTTPS required", Response.Status.FORBIDDEN); |
| | | } |
| | | } |
| | | |
| | | private void checkRealm() { |
| | | if (!realm.isEnabled()) { |
| | | throw new ErrorResponseException("access_denied", "Realm not enabled", Response.Status.FORBIDDEN); |
| | | } |
| | | } |
| | | |
| | | private void checkClient(String service) { |
| | | if (service == null) { |
| | | event.error(Errors.INVALID_REQUEST); |
| | | throw new ErrorPageException(session, Messages.MISSING_PARAMETER, CASLoginProtocol.SERVICE_PARAM); |
| | | } |
| | | |
| | | client = realm.getClients().stream() |
| | | .filter(c -> CASLoginProtocol.LOGIN_PROTOCOL.equals(c.getProtocol())) |
| | | .filter(c -> RedirectUtils.verifyRedirectUri(uriInfo, service, realm, c) != null) |
| | | .findFirst().orElse(null); |
| | | if (client == null) { |
| | | event.error(Errors.CLIENT_NOT_FOUND); |
| | | throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND); |
| | | } |
| | | |
| | | if (!client.isEnabled()) { |
| | | event.error(Errors.CLIENT_DISABLED); |
| | | throw new ErrorPageException(session, Messages.CLIENT_DISABLED); |
| | | } |
| | | |
| | | if (client.isBearerOnly()) { |
| | | event.error(Errors.NOT_ALLOWED); |
| | | throw new ErrorPageException(session, Messages.BEARER_ONLY); |
| | | } |
| | | |
| | | event.client(client.getClientId()); |
| | | |
| | | session.getContext().setClient(client); |
| | | } |
| | | |
| | | private void checkTicket(String ticket, boolean requireReauth) { |
| | | if (ticket == null || !ticket.startsWith(CASLoginProtocol.SERVICE_TICKET_PREFIX)) { |
| | | event.error(Errors.INVALID_CODE); |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing or invalid parameter: " + CASLoginProtocol.TICKET_PARAM, Response.Status.BAD_REQUEST); |
| | | } |
| | | |
| | | String code = ticket.substring(CASLoginProtocol.SERVICE_TICKET_PREFIX.length()); |
| | | |
| | | ClientSessionCode.ParseResult parseResult = ClientSessionCode.parseResult(code, session, realm); |
| | | if (parseResult.isClientSessionNotFound() || parseResult.isIllegalHash()) { |
| | | String[] parts = code.split("\\."); |
| | | if (parts.length == 2) { |
| | | event.detail(Details.CODE_ID, parts[1]); |
| | | } |
| | | event.error(Errors.INVALID_CODE); |
| | | if (parseResult.getClientSession() != null) { |
| | | session.sessions().removeClientSession(realm, parseResult.getClientSession()); |
| | | } |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code not valid", Response.Status.BAD_REQUEST); |
| | | } |
| | | |
| | | clientSession = parseResult.getClientSession(); |
| | | event.detail(Details.CODE_ID, clientSession.getId()); |
| | | |
| | | if (!parseResult.getCode().isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) { |
| | | event.error(Errors.INVALID_CODE); |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST); |
| | | } |
| | | |
| | | parseResult.getCode().setAction(null); |
| | | |
| | | UserSessionModel userSession = clientSession.getUserSession(); |
| | | |
| | | if (userSession == null) { |
| | | event.error(Errors.USER_SESSION_NOT_FOUND); |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User session not found", Response.Status.BAD_REQUEST); |
| | | } |
| | | |
| | | UserModel user = userSession.getUser(); |
| | | if (user == null) { |
| | | event.error(Errors.USER_NOT_FOUND); |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User not found", Response.Status.BAD_REQUEST); |
| | | } |
| | | if (!user.isEnabled()) { |
| | | event.error(Errors.USER_DISABLED); |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User disabled", Response.Status.BAD_REQUEST); |
| | | } |
| | | |
| | | event.user(userSession.getUser()); |
| | | event.session(userSession.getId()); |
| | | |
| | | if (!client.getClientId().equals(clientSession.getClient().getClientId())) { |
| | | event.error(Errors.INVALID_CODE); |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Auth error", Response.Status.BAD_REQUEST); |
| | | } |
| | | |
| | | if (!AuthenticationManager.isSessionValid(realm, userSession)) { |
| | | event.error(Errors.USER_SESSION_NOT_FOUND); |
| | | throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Session not active", Response.Status.BAD_REQUEST); |
| | | } |
| | | |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.installation; |
| | | |
| | | import org.keycloak.Config; |
| | | import org.keycloak.models.ClientModel; |
| | | import org.keycloak.models.KeycloakSession; |
| | | import org.keycloak.models.KeycloakSessionFactory; |
| | | import org.keycloak.models.RealmModel; |
| | | import org.keycloak.protocol.ClientInstallationProvider; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | |
| | | import javax.ws.rs.core.MediaType; |
| | | import javax.ws.rs.core.Response; |
| | | import java.net.URI; |
| | | |
| | | public class KeycloakCASClientInstallation implements ClientInstallationProvider { |
| | | |
| | | @Override |
| | | public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) { |
| | | return Response.ok("{}", MediaType.TEXT_PLAIN_TYPE).build(); |
| | | } |
| | | |
| | | @Override |
| | | public String getProtocol() { |
| | | return CASLoginProtocol.LOGIN_PROTOCOL; |
| | | } |
| | | |
| | | @Override |
| | | public String getDisplayType() { |
| | | return "Keycloak CAS JSON"; |
| | | } |
| | | |
| | | @Override |
| | | public String getHelpText() { |
| | | return "keycloak.json file used by the Keycloak CAS client adapter to configure clients. This must be saved to a keycloak.json file and put in your WEB-INF directory of your WAR file. You may also want to tweak this file after you download it."; |
| | | } |
| | | |
| | | @Override |
| | | public void close() { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public ClientInstallationProvider create(KeycloakSession session) { |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | public void init(Config.Scope config) { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void postInit(KeycloakSessionFactory factory) { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public String getId() { |
| | | return "keycloak-cas-keycloak-json"; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isDownloadOnly() { |
| | | return false; |
| | | } |
| | | |
| | | @Override |
| | | public String getFilename() { |
| | | return "keycloak.json"; |
| | | } |
| | | |
| | | @Override |
| | | public String getMediaType() { |
| | | return MediaType.APPLICATION_JSON; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.Config; |
| | | import org.keycloak.models.KeycloakSession; |
| | | import org.keycloak.models.KeycloakSessionFactory; |
| | | import org.keycloak.protocol.ProtocolMapper; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | |
| | | public abstract class AbstractCASProtocolMapper implements ProtocolMapper { |
| | | public static final String TOKEN_MAPPER_CATEGORY = "Token mapper"; |
| | | |
| | | @Override |
| | | public String getProtocol() { |
| | | return CASLoginProtocol.LOGIN_PROTOCOL; |
| | | } |
| | | |
| | | @Override |
| | | public void close() { |
| | | } |
| | | |
| | | @Override |
| | | public final ProtocolMapper create(KeycloakSession session) { |
| | | throw new RuntimeException("UNSUPPORTED METHOD"); |
| | | } |
| | | |
| | | @Override |
| | | public void init(Config.Scope config) { |
| | | } |
| | | |
| | | @Override |
| | | public void postInit(KeycloakSessionFactory factory) { |
| | | } |
| | | |
| | | @Override |
| | | public String getDisplayCategory() { |
| | | return TOKEN_MAPPER_CATEGORY; |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.JSON_TYPE; |
| | | import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME; |
| | | |
| | | public class FullNameMapper extends AbstractCASProtocolMapper { |
| | | private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); |
| | | |
| | | static { |
| | | OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); |
| | | } |
| | | |
| | | public static final String PROVIDER_ID = "cas-full-name-mapper"; |
| | | |
| | | |
| | | @Override |
| | | public List<ProviderConfigProperty> getConfigProperties() { |
| | | return configProperties; |
| | | } |
| | | |
| | | @Override |
| | | public String getId() { |
| | | return PROVIDER_ID; |
| | | } |
| | | |
| | | @Override |
| | | public String getDisplayType() { |
| | | return "User's full name"; |
| | | } |
| | | |
| | | @Override |
| | | public String getHelpText() { |
| | | return "Maps the user's first and last name to the OpenID Connect 'name' claim. Format is <first> + ' ' + <last>"; |
| | | } |
| | | |
| | | public static ProtocolMapperModel create(String name, String tokenClaimName, |
| | | boolean consentRequired, String consentText) { |
| | | ProtocolMapperModel mapper = new ProtocolMapperModel(); |
| | | mapper.setName(name); |
| | | mapper.setProtocolMapper(PROVIDER_ID); |
| | | mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); |
| | | mapper.setConsentRequired(consentRequired); |
| | | mapper.setConsentText(consentText); |
| | | Map<String, String> config = new HashMap<String, String>(); |
| | | config.put(TOKEN_CLAIM_NAME, tokenClaimName); |
| | | mapper.setConfig(config); |
| | | return mapper; |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | import org.keycloak.protocol.oidc.OIDCLoginProtocol; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class GroupMembershipMapper extends AbstractCASProtocolMapper { |
| | | private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); |
| | | |
| | | static { |
| | | OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); |
| | | ProviderConfigProperty property1 = new ProviderConfigProperty(); |
| | | property1.setName("full.path"); |
| | | property1.setLabel("Full group path"); |
| | | property1.setType(ProviderConfigProperty.BOOLEAN_TYPE); |
| | | property1.setDefaultValue("true"); |
| | | property1.setHelpText("Include full path to group i.e. /top/level1/level2, false will just specify the group name"); |
| | | configProperties.add(property1); |
| | | } |
| | | |
| | | public static final String PROVIDER_ID = "cas-group-membership-mapper"; |
| | | |
| | | |
| | | @Override |
| | | public List<ProviderConfigProperty> getConfigProperties() { |
| | | return configProperties; |
| | | } |
| | | |
| | | @Override |
| | | public String getId() { |
| | | return PROVIDER_ID; |
| | | } |
| | | |
| | | @Override |
| | | public String getDisplayType() { |
| | | return "Group Membership"; |
| | | } |
| | | |
| | | @Override |
| | | public String getHelpText() { |
| | | return "Map user group membership"; |
| | | } |
| | | |
| | | public static boolean useFullPath(ProtocolMapperModel mappingModel) { |
| | | return "true".equals(mappingModel.getConfig().get("full.path")); |
| | | } |
| | | |
| | | public static ProtocolMapperModel create(String name, String tokenClaimName, |
| | | boolean consentRequired, String consentText, boolean fullPath) { |
| | | ProtocolMapperModel mapper = new ProtocolMapperModel(); |
| | | mapper.setName(name); |
| | | mapper.setProtocolMapper(PROVIDER_ID); |
| | | mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); |
| | | mapper.setConsentRequired(consentRequired); |
| | | mapper.setConsentText(consentText); |
| | | Map<String, String> config = new HashMap<String, String>(); |
| | | config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName); |
| | | config.put("full.path", Boolean.toString(fullPath)); |
| | | mapper.setConfig(config); |
| | | |
| | | return mapper; |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | public class HardcodedClaim extends AbstractCASProtocolMapper { |
| | | private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); |
| | | |
| | | public static final String CLAIM_VALUE = "claim.value"; |
| | | |
| | | static { |
| | | OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); |
| | | |
| | | ProviderConfigProperty property = new ProviderConfigProperty(); |
| | | property.setName(CLAIM_VALUE); |
| | | property.setLabel("Claim value"); |
| | | property.setType(ProviderConfigProperty.STRING_TYPE); |
| | | property.setHelpText("Value of the claim you want to hard code. 'true' and 'false can be used for boolean values."); |
| | | configProperties.add(property); |
| | | |
| | | OIDCAttributeMapperHelper.addJsonTypeConfig(configProperties); |
| | | } |
| | | |
| | | public static final String PROVIDER_ID = "cas-hardcoded-claim-mapper"; |
| | | |
| | | |
| | | @Override |
| | | public List<ProviderConfigProperty> getConfigProperties() { |
| | | return configProperties; |
| | | } |
| | | |
| | | @Override |
| | | public String getId() { |
| | | return PROVIDER_ID; |
| | | } |
| | | |
| | | @Override |
| | | public String getDisplayType() { |
| | | return "Hardcoded claim"; |
| | | } |
| | | |
| | | @Override |
| | | public String getHelpText() { |
| | | return "Hardcode a claim into the token."; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.JSON_TYPE; |
| | | import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME; |
| | | |
| | | public class UserAttributeMapper extends AbstractCASProtocolMapper { |
| | | private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); |
| | | |
| | | static { |
| | | ProviderConfigProperty property; |
| | | property = new ProviderConfigProperty(); |
| | | property.setName(ProtocolMapperUtils.USER_ATTRIBUTE); |
| | | property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL); |
| | | property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); |
| | | property.setType(ProviderConfigProperty.STRING_TYPE); |
| | | configProperties.add(property); |
| | | OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); |
| | | OIDCAttributeMapperHelper.addJsonTypeConfig(configProperties); |
| | | |
| | | property = new ProviderConfigProperty(); |
| | | property.setName(ProtocolMapperUtils.MULTIVALUED); |
| | | property.setLabel(ProtocolMapperUtils.MULTIVALUED_LABEL); |
| | | property.setHelpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT); |
| | | property.setType(ProviderConfigProperty.BOOLEAN_TYPE); |
| | | configProperties.add(property); |
| | | |
| | | } |
| | | |
| | | public static final String PROVIDER_ID = "cas-usermodel-attribute-mapper"; |
| | | |
| | | |
| | | @Override |
| | | public List<ProviderConfigProperty> getConfigProperties() { |
| | | return configProperties; |
| | | } |
| | | |
| | | @Override |
| | | public String getId() { |
| | | return PROVIDER_ID; |
| | | } |
| | | |
| | | @Override |
| | | public String getDisplayType() { |
| | | return "User Attribute"; |
| | | } |
| | | |
| | | @Override |
| | | public String getHelpText() { |
| | | return "Map a custom user attribute to a token claim."; |
| | | } |
| | | |
| | | public static ProtocolMapperModel create(String name, String userAttribute, |
| | | String tokenClaimName, String claimType, |
| | | boolean consentRequired, String consentText, boolean multivalued) { |
| | | ProtocolMapperModel mapper = new ProtocolMapperModel(); |
| | | mapper.setName(name); |
| | | mapper.setProtocolMapper(PROVIDER_ID); |
| | | mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); |
| | | mapper.setConsentRequired(consentRequired); |
| | | mapper.setConsentText(consentText); |
| | | Map<String, String> config = new HashMap<String, String>(); |
| | | config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute); |
| | | config.put(TOKEN_CLAIM_NAME, tokenClaimName); |
| | | config.put(JSON_TYPE, claimType); |
| | | if (multivalued) { |
| | | mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, "true"); |
| | | } |
| | | mapper.setConfig(config); |
| | | return mapper; |
| | | } |
| | | } |
New file |
| | |
| | | package org.keycloak.protocol.cas.mappers; |
| | | |
| | | import org.keycloak.models.ProtocolMapperModel; |
| | | import org.keycloak.protocol.ProtocolMapperUtils; |
| | | import org.keycloak.protocol.cas.CASLoginProtocol; |
| | | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; |
| | | import org.keycloak.provider.ProviderConfigProperty; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.JSON_TYPE; |
| | | import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME; |
| | | |
| | | public class UserPropertyMapper extends AbstractCASProtocolMapper { |
| | | private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); |
| | | |
| | | static { |
| | | ProviderConfigProperty property; |
| | | property = new ProviderConfigProperty(); |
| | | property.setName(ProtocolMapperUtils.USER_ATTRIBUTE); |
| | | property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL); |
| | | property.setType(ProviderConfigProperty.STRING_TYPE); |
| | | property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); |
| | | configProperties.add(property); |
| | | OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties); |
| | | OIDCAttributeMapperHelper.addJsonTypeConfig(configProperties); |
| | | } |
| | | |
| | | public static final String PROVIDER_ID = "cas-usermodel-property-mapper"; |
| | | |
| | | |
| | | @Override |
| | | public List<ProviderConfigProperty> getConfigProperties() { |
| | | return configProperties; |
| | | } |
| | | |
| | | @Override |
| | | public String getId() { |
| | | return PROVIDER_ID; |
| | | } |
| | | |
| | | @Override |
| | | public String getDisplayType() { |
| | | return "User Property"; |
| | | } |
| | | |
| | | @Override |
| | | public String getHelpText() { |
| | | return "Map a built in user property (email, firstName, lastName) to a token claim."; |
| | | } |
| | | |
| | | public static ProtocolMapperModel create(String name, String userAttribute, |
| | | String tokenClaimName, String claimType, |
| | | boolean consentRequired, String consentText) { |
| | | ProtocolMapperModel mapper = new ProtocolMapperModel(); |
| | | mapper.setName(name); |
| | | mapper.setProtocolMapper(PROVIDER_ID); |
| | | mapper.setProtocol(CASLoginProtocol.LOGIN_PROTOCOL); |
| | | mapper.setConsentRequired(consentRequired); |
| | | mapper.setConsentText(consentText); |
| | | Map<String, String> config = new HashMap<String, String>(); |
| | | config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute); |
| | | config.put(TOKEN_CLAIM_NAME, tokenClaimName); |
| | | config.put(JSON_TYPE, claimType); |
| | | mapper.setConfig(config); |
| | | return mapper; |
| | | } |
| | | } |
New file |
| | |
| | | # |
| | | # Copyright 2016 Red Hat, Inc. and/or its affiliates |
| | | # and other contributors as indicated by the @author tags. |
| | | # |
| | | # Licensed under the Apache License, Version 2.0 (the "License"); |
| | | # you may not use this file except in compliance with the License. |
| | | # You may obtain a copy of the License at |
| | | # |
| | | # http://www.apache.org/licenses/LICENSE-2.0 |
| | | # |
| | | # Unless required by applicable law or agreed to in writing, software |
| | | # distributed under the License is distributed on an "AS IS" BASIS, |
| | | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | # See the License for the specific language governing permissions and |
| | | # limitations under the License. |
| | | # |
| | | |
| | | org.keycloak.protocol.cas.installation.KeycloakCASClientInstallation |
New file |
| | |
| | | # |
| | | # Copyright 2016 Red Hat, Inc. and/or its affiliates |
| | | # and other contributors as indicated by the @author tags. |
| | | # |
| | | # Licensed under the Apache License, Version 2.0 (the "License"); |
| | | # you may not use this file except in compliance with the License. |
| | | # You may obtain a copy of the License at |
| | | # |
| | | # http://www.apache.org/licenses/LICENSE-2.0 |
| | | # |
| | | # Unless required by applicable law or agreed to in writing, software |
| | | # distributed under the License is distributed on an "AS IS" BASIS, |
| | | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | # See the License for the specific language governing permissions and |
| | | # limitations under the License. |
| | | # |
| | | |
| | | org.keycloak.protocol.cas.CASLoginProtocolFactory |
New file |
| | |
| | | # |
| | | # Copyright 2016 Red Hat, Inc. and/or its affiliates |
| | | # and other contributors as indicated by the @author tags. |
| | | # |
| | | # Licensed under the Apache License, Version 2.0 (the "License"); |
| | | # you may not use this file except in compliance with the License. |
| | | # You may obtain a copy of the License at |
| | | # |
| | | # http://www.apache.org/licenses/LICENSE-2.0 |
| | | # |
| | | # Unless required by applicable law or agreed to in writing, software |
| | | # distributed under the License is distributed on an "AS IS" BASIS, |
| | | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | # See the License for the specific language governing permissions and |
| | | # limitations under the License. |
| | | # |
| | | |
| | | org.keycloak.protocol.cas.mappers.FullNameMapper |
| | | org.keycloak.protocol.cas.mappers.GroupMembershipMapper |
| | | org.keycloak.protocol.cas.mappers.HardcodedClaim |
| | | org.keycloak.protocol.cas.mappers.UserAttributeMapper |
| | | org.keycloak.protocol.cas.mappers.UserPropertyMapper |