KEYCLOAK-6229 OpenShift Token Review interface
This commit is contained in:
parent
1fb4ca4525
commit
bf758809ba
22 changed files with 1073 additions and 8 deletions
|
@ -33,7 +33,13 @@ import java.util.Set;
|
|||
public class Profile {
|
||||
|
||||
public enum Feature {
|
||||
AUTHORIZATION, IMPERSONATION, SCRIPTS, DOCKER, ACCOUNT2, TOKEN_EXCHANGE
|
||||
ACCOUNT2,
|
||||
AUTHORIZATION,
|
||||
DOCKER,
|
||||
IMPERSONATION,
|
||||
OPENSHIFT_INTEGRATION,
|
||||
SCRIPTS,
|
||||
TOKEN_EXCHANGE
|
||||
}
|
||||
|
||||
private enum ProductValue {
|
||||
|
|
|
@ -76,6 +76,8 @@ public interface KeycloakSession {
|
|||
Class<? extends Provider> getProviderClass(String providerClassName);
|
||||
|
||||
Object getAttribute(String attribute);
|
||||
<T> T getAttribute(String attribute, Class<T> clazz);
|
||||
|
||||
Object removeAttribute(String attribute);
|
||||
void setAttribute(String name, Object value);
|
||||
|
||||
|
|
|
@ -88,6 +88,10 @@ public class ClientIdAndSecretAuthenticator extends AbstractClientAuthenticator
|
|||
clientSecret = formData.getFirst(OAuth2Constants.CLIENT_SECRET);
|
||||
}
|
||||
|
||||
if (client_id == null) {
|
||||
client_id = context.getSession().getAttribute("client_id", String.class);
|
||||
}
|
||||
|
||||
if (client_id == null) {
|
||||
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
|
||||
context.challenge(challengeResponse);
|
||||
|
|
|
@ -66,6 +66,10 @@ public class X509ClientAuthenticator extends AbstractClientAuthenticator {
|
|||
client_id = queryParams.getFirst(OAuth2Constants.CLIENT_ID);
|
||||
}
|
||||
|
||||
if (client_id == null) {
|
||||
client_id = context.getSession().getAttribute("client_id", String.class);
|
||||
}
|
||||
|
||||
if (client_id == null) {
|
||||
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
|
||||
context.challenge(challengeResponse);
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.keycloak.protocol.oidc.endpoints.LoginStatusIframeEndpoint;
|
|||
import org.keycloak.protocol.oidc.endpoints.LogoutEndpoint;
|
||||
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
||||
import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
|
||||
import org.keycloak.protocol.oidc.ext.OIDCExtProvider;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
|
@ -42,8 +43,10 @@ import org.keycloak.services.resources.RealmsResource;
|
|||
import org.keycloak.services.util.CacheControlUtil;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.OPTIONS;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
|
@ -258,4 +261,14 @@ public class OIDCLoginProtocolService {
|
|||
}
|
||||
}
|
||||
|
||||
@Path("ext/{extension}")
|
||||
public Object resolveExtension(@PathParam("extension") String extension) {
|
||||
OIDCExtProvider provider = session.getProvider(OIDCExtProvider.class, extension);
|
||||
if (provider != null) {
|
||||
provider.setEvent(event);
|
||||
return provider;
|
||||
}
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package org.keycloak.protocol.oidc.ext;
|
||||
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
public interface OIDCExtProvider extends Provider {
|
||||
|
||||
void setEvent(EventBuilder event);
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.keycloak.protocol.oidc.ext;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface OIDCExtProviderFactory extends ProviderFactory<OIDCExtProvider> {
|
||||
|
||||
@Override
|
||||
default void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
default void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
default int order() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.keycloak.protocol.oidc.ext;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
public class OIDCExtSPI implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "openid-connect-ext";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return OIDCExtProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return OIDCExtProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.keycloak.protocol.openshift;
|
||||
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
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.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.ext.OIDCExtProvider;
|
||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenShiftTokenReviewEndpoint implements OIDCExtProvider {
|
||||
|
||||
private KeycloakSession session;
|
||||
private TokenManager tokenManager;
|
||||
private EventBuilder event;
|
||||
|
||||
public OpenShiftTokenReviewEndpoint(KeycloakSession session) {
|
||||
this.session = session;
|
||||
this.tokenManager = new TokenManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEvent(EventBuilder event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@Path("/")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response tokenReview(OpenShiftTokenReviewRequestRepresentation reviewRequest) throws Exception {
|
||||
return tokenReview(null, reviewRequest);
|
||||
}
|
||||
|
||||
@Path("/{client_id}")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response tokenReview(@PathParam("client_id") String clientId, OpenShiftTokenReviewRequestRepresentation reviewRequest) throws Exception {
|
||||
event.event(EventType.INTROSPECT_TOKEN);
|
||||
|
||||
if (clientId != null) {
|
||||
session.setAttribute("client_id", clientId);
|
||||
}
|
||||
|
||||
checkSsl();
|
||||
checkRealm();
|
||||
authorizeClient();
|
||||
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
AccessToken token = null;
|
||||
try {
|
||||
RSATokenVerifier verifier = RSATokenVerifier.create(reviewRequest.getSpec().getToken())
|
||||
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
|
||||
|
||||
PublicKey publicKey = session.keys().getRsaPublicKey(realm, verifier.getHeader().getKeyId());
|
||||
if (publicKey == null) {
|
||||
error(401, Errors.INVALID_TOKEN, "Invalid public key");
|
||||
} else {
|
||||
verifier.publicKey(publicKey);
|
||||
verifier.verify();
|
||||
token = verifier.getToken();
|
||||
}
|
||||
} catch (VerificationException e) {
|
||||
error(401, Errors.INVALID_TOKEN, "Token verification failure");
|
||||
}
|
||||
|
||||
if (!tokenManager.isTokenValid(session, realm, token)) {
|
||||
error(401, Errors.INVALID_TOKEN, "Token verification failure");
|
||||
}
|
||||
|
||||
OpenShiftTokenReviewResponseRepresentation response = new OpenShiftTokenReviewResponseRepresentation();
|
||||
response.getStatus().setAuthenticated(true);
|
||||
response.getStatus().setUser(new OpenShiftTokenReviewResponseRepresentation.User());
|
||||
|
||||
OpenShiftTokenReviewResponseRepresentation.User userRep = response.getStatus().getUser();
|
||||
userRep.setUid(token.getSubject());
|
||||
userRep.setUsername(token.getPreferredUsername());
|
||||
|
||||
if (token.getScope() != null && !token.getScope().isEmpty()) {
|
||||
OpenShiftTokenReviewResponseRepresentation.Extra extra = new OpenShiftTokenReviewResponseRepresentation.Extra();
|
||||
extra.setScopes(token.getScope().split(" "));
|
||||
userRep.setExtra(extra);
|
||||
}
|
||||
|
||||
if (token.getOtherClaims() != null && token.getOtherClaims().get("groups") != null) {
|
||||
List<String> groups = (List<String>) token.getOtherClaims().get("groups");
|
||||
userRep.setGroups(groups);
|
||||
}
|
||||
|
||||
event.success();
|
||||
return Response.ok(response, MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
private void checkSsl() {
|
||||
if (!session.getContext().getUri().getBaseUri().getScheme().equals("https") && session.getContext().getRealm().getSslRequired().isRequired(session.getContext().getConnection())) {
|
||||
error(401, Errors.SSL_REQUIRED, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRealm() {
|
||||
if (!session.getContext().getRealm().isEnabled()) {
|
||||
error(401, Errors.REALM_DISABLED,null);
|
||||
}
|
||||
}
|
||||
|
||||
private void authorizeClient() {
|
||||
try {
|
||||
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
|
||||
event.client(client);
|
||||
|
||||
if (client == null || client.isPublicClient()) {
|
||||
error(401, Errors.INVALID_CLIENT, "Public client is not permitted to invoke token review endpoint");
|
||||
}
|
||||
|
||||
} catch (ErrorResponseException ere) {
|
||||
error(401, Errors.INVALID_CLIENT_CREDENTIALS, ere.getErrorDescription());
|
||||
} catch (Exception e) {
|
||||
error(401, Errors.INVALID_CLIENT_CREDENTIALS, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void error(int statusCode, String error, String description) {
|
||||
OpenShiftTokenReviewResponseRepresentation rep = new OpenShiftTokenReviewResponseRepresentation();
|
||||
rep.getStatus().setAuthenticated(false);
|
||||
|
||||
Response response = Response.status(statusCode).entity(rep).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
|
||||
event.error(error);
|
||||
event.detail(Details.REASON, description);
|
||||
|
||||
throw new ErrorResponseException(response);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.keycloak.protocol.openshift;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.ext.OIDCExtProvider;
|
||||
import org.keycloak.protocol.oidc.ext.OIDCExtProviderFactory;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenShiftTokenReviewEndpointFactory implements OIDCExtProviderFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
@Override
|
||||
public OIDCExtProvider create(KeycloakSession session) {
|
||||
return new OpenShiftTokenReviewEndpoint(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "openshift-token-review";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.OPENSHIFT_INTEGRATION);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.openshift;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OpenShiftTokenReviewRequestRepresentation implements Serializable {
|
||||
|
||||
@JsonProperty("apiVersion")
|
||||
private String apiVersion = "authentication.k8s.io/v1beta1";
|
||||
|
||||
@JsonProperty("kind")
|
||||
private String kind = "TokenReview";
|
||||
|
||||
@JsonProperty("spec")
|
||||
private Spec spec;
|
||||
|
||||
public String getApiVersion() {
|
||||
return apiVersion;
|
||||
}
|
||||
|
||||
public void setApiVersion(String apiVersion) {
|
||||
this.apiVersion = apiVersion;
|
||||
}
|
||||
|
||||
public String getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public void setKind(String kind) {
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public Spec getSpec() {
|
||||
return spec;
|
||||
}
|
||||
|
||||
public void setSpec(Spec spec) {
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public static class Spec implements Serializable {
|
||||
|
||||
@JsonProperty("token")
|
||||
private String token;
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.openshift;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenShiftTokenReviewResponseRepresentation implements Serializable {
|
||||
|
||||
@JsonProperty("apiVersion")
|
||||
private String apiVersion = "authentication.k8s.io/v1beta1";
|
||||
|
||||
@JsonProperty("kind")
|
||||
private String kind = "TokenReview";
|
||||
|
||||
@JsonProperty("status")
|
||||
private Status status = new Status();
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getApiVersion() {
|
||||
return apiVersion;
|
||||
}
|
||||
|
||||
public void setApiVersion(String apiVersion) {
|
||||
this.apiVersion = apiVersion;
|
||||
}
|
||||
|
||||
public String getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public void setKind(String kind) {
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public static class Status implements Serializable {
|
||||
|
||||
@JsonProperty("authenticated")
|
||||
private boolean authenticated;
|
||||
|
||||
@JsonProperty("user")
|
||||
protected User user;
|
||||
|
||||
public boolean isAuthenticated() {
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
public void setAuthenticated(boolean authenticated) {
|
||||
this.authenticated = authenticated;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class User implements Serializable {
|
||||
|
||||
@JsonProperty("username")
|
||||
protected String username;
|
||||
|
||||
@JsonProperty("uid")
|
||||
protected String uid;
|
||||
|
||||
@JsonProperty("groups")
|
||||
protected List<String> groups = new LinkedList<>();
|
||||
|
||||
@JsonProperty("extra")
|
||||
protected Extra extra;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public List<String> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(List<String> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public Extra getExtra() {
|
||||
return extra;
|
||||
}
|
||||
|
||||
public void setExtra(Extra extra) {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Extra implements Serializable {
|
||||
|
||||
@JsonProperty("scopes.authorization.openshift.io")
|
||||
private String[] scopes;
|
||||
|
||||
public String[] getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public void setScopes(String[] scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -105,6 +105,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
return attributes.get(attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attribute, Class<T> clazz) {
|
||||
Object value = getAttribute(attribute);
|
||||
return value != null && clazz.isInstance(value) ? (T) value : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String attribute) {
|
||||
return attributes.remove(attribute);
|
||||
|
|
|
@ -28,20 +28,37 @@ import javax.ws.rs.core.Response;
|
|||
*/
|
||||
public class ErrorResponseException extends WebApplicationException {
|
||||
|
||||
private final Response response;
|
||||
private final String error;
|
||||
private final String errorDescription;
|
||||
private final Response.Status status;
|
||||
|
||||
public ErrorResponseException(String error, String errorDescription, Response.Status status) {
|
||||
this.response = null;
|
||||
this.error = error;
|
||||
this.errorDescription = errorDescription;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public ErrorResponseException(Response response) {
|
||||
this.response = response;
|
||||
this.error = null;
|
||||
this.errorDescription = null;
|
||||
this.status = null;
|
||||
}
|
||||
|
||||
public String getErrorDescription() {
|
||||
return errorDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
if (response != null) {
|
||||
return response;
|
||||
} else {
|
||||
OAuth2ErrorRepresentation errorRep = new OAuth2ErrorRepresentation(error, errorDescription);
|
||||
return Response.status(status).entity(errorRep).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.protocol.openshift.OpenShiftTokenReviewEndpointFactory
|
|
@ -21,3 +21,4 @@ org.keycloak.services.clientregistration.ClientRegistrationSpi
|
|||
org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi
|
||||
org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi
|
||||
org.keycloak.services.x509.X509ClientCertificateLookupSpi
|
||||
org.keycloak.protocol.oidc.ext.OIDCExtSPI
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.forms;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.ClientAuthenticationFlowContext;
|
||||
import org.keycloak.authentication.FlowStatus;
|
||||
import org.keycloak.authentication.authenticators.client.AbstractClientAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class DummyClientAuthenticator extends AbstractClientAuthenticator {
|
||||
|
||||
public static final String PROVIDER_ID = "testsuite-client-dummy";
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.ALTERNATIVE
|
||||
};
|
||||
|
||||
@Override
|
||||
public void authenticateClient(ClientAuthenticationFlowContext context) {
|
||||
ClientIdAndSecretAuthenticator authenticator = new ClientIdAndSecretAuthenticator();
|
||||
authenticator.authenticateClient(context);
|
||||
if (context.getStatus().equals(FlowStatus.SUCCESS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String clientId = context.getUriInfo().getQueryParameters().getFirst("client_id");
|
||||
|
||||
if (clientId == null) {
|
||||
clientId = context.getSession().getAttribute("client_id", String.class);
|
||||
}
|
||||
|
||||
ClientModel client = context.getRealm().getClientByClientId(clientId);
|
||||
if (client == null) {
|
||||
context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
|
||||
return;
|
||||
}
|
||||
|
||||
context.getEvent().client(client);
|
||||
context.setClient(client);
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Testsuite ClientId Dummy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Dummy client authenticator, which authenticates the client with clientId only";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigPropertiesPerClient() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAdapterConfiguration(ClientModel client) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getProtocolAuthenticatorMethods(String loginProtocol) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
|
@ -41,7 +41,9 @@ public class PassThroughClientAuthenticator extends AbstractClientAuthenticator
|
|||
public static String clientId = "test-app";
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED
|
||||
};
|
||||
|
||||
private static final List<ProviderConfigProperty> clientConfigProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
|
|
@ -16,3 +16,4 @@
|
|||
#
|
||||
|
||||
org.keycloak.testsuite.forms.PassThroughClientAuthenticator
|
||||
org.keycloak.testsuite.forms.DummyClientAuthenticator
|
|
@ -148,6 +148,8 @@ public class OAuthClient {
|
|||
private String codeChallengeMethod;
|
||||
private String origin;
|
||||
|
||||
private boolean openid = true;
|
||||
|
||||
private Supplier<CloseableHttpClient> httpClient = OAuthClient::newCloseableHttpClient;
|
||||
|
||||
public class LogoutUrlBuilder {
|
||||
|
@ -212,6 +214,7 @@ public class OAuthClient {
|
|||
codeChallenge = null;
|
||||
codeChallengeMethod = null;
|
||||
origin = null;
|
||||
openid = true;
|
||||
}
|
||||
|
||||
public void setDriver(WebDriver driver) {
|
||||
|
@ -773,8 +776,10 @@ public class OAuthClient {
|
|||
b.queryParam(OIDCLoginProtocol.NONCE_PARAM, nonce);
|
||||
}
|
||||
|
||||
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
||||
String scopeParam = openid ? TokenUtil.attachOIDCScope(scope) : scope;
|
||||
if (scopeParam != null && !scopeParam.isEmpty()) {
|
||||
b.queryParam(OAuth2Constants.SCOPE, scopeParam);
|
||||
}
|
||||
|
||||
if (maxAge != null) {
|
||||
b.queryParam(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge);
|
||||
|
@ -883,6 +888,11 @@ public class OAuthClient {
|
|||
return this;
|
||||
}
|
||||
|
||||
public OAuthClient openid(boolean openid) {
|
||||
this.openid = openid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OAuthClient uiLocales(String uiLocales){
|
||||
this.uiLocales = uiLocales;
|
||||
return this;
|
||||
|
|
|
@ -81,12 +81,13 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
|||
"'client_secret' sent either in request parameters or in 'Authorization: Basic' header");
|
||||
addProviderInfo(expected, "testsuite-client-passthrough", "Testsuite Dummy Client Validation", "Testsuite dummy authenticator, " +
|
||||
"which automatically authenticates hardcoded client (like 'test-app' )");
|
||||
addProviderInfo(expected, "testsuite-client-dummy", "Testsuite ClientId Dummy",
|
||||
"Dummy client authenticator, which authenticates the client with clientId only");
|
||||
addProviderInfo(expected, "client-x509", "X509 Certificate",
|
||||
"Validates client based on a X509 Certificate");
|
||||
addProviderInfo(expected, "client-secret-jwt", "Signed Jwt with Client Secret",
|
||||
"Validates client based on signed JWT issued by client and signed with the Client Secret");
|
||||
|
||||
|
||||
compareProviders(expected, result);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,354 @@
|
|||
package org.keycloak.testsuite.openshift;
|
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
|
||||
import org.keycloak.protocol.openshift.OpenShiftTokenReviewRequestRepresentation;
|
||||
import org.keycloak.protocol.openshift.OpenShiftTokenReviewResponseRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class OpenShiftTokenReviewEndpointTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
private static boolean flowConfigured;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
ClientRepresentation client = testRealm.getClients().stream().filter(r -> r.getClientId().equals("test-app")).findFirst().get();
|
||||
|
||||
List<ProtocolMapperRepresentation> mappers = new LinkedList<>();
|
||||
ProtocolMapperRepresentation mapper = new ProtocolMapperRepresentation();
|
||||
mapper.setName("groups");
|
||||
mapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID);
|
||||
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put("full.path", "false");
|
||||
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "groups");
|
||||
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
|
||||
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
|
||||
mapper.setConfig(config);
|
||||
mappers.add(mapper);
|
||||
|
||||
client.setProtocolMappers(mappers);
|
||||
client.setPublicClient(false);
|
||||
client.setClientAuthenticatorType("testsuite-client-dummy");
|
||||
|
||||
testRealm.getUsers().add(UserBuilder.create().username("groups-user").password("password").addGroups("/topGroup", "/topGroup/level2group").build());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void enablePassthroughAuthenticator() {
|
||||
if (!flowConfigured) {
|
||||
HashMap<String, String> data = new HashMap<>();
|
||||
data.put("newName", "testsuite-client-dummy");
|
||||
Response response = testRealm().flows().copy("clients", data);
|
||||
assertEquals(201, response.getStatus());
|
||||
response.close();
|
||||
|
||||
data = new HashMap<>();
|
||||
data.put("provider", "testsuite-client-dummy");
|
||||
data.put("requirement", "ALTERNATIVE");
|
||||
|
||||
testRealm().flows().addExecution("testsuite-client-dummy", data);
|
||||
|
||||
RealmRepresentation realmRep = testRealm().toRepresentation();
|
||||
realmRep.setClientAuthenticationFlow("testsuite-client-dummy");
|
||||
testRealm().update(realmRep);
|
||||
|
||||
List<AuthenticationExecutionInfoRepresentation> executions = testRealm().flows().getExecutions("testsuite-client-dummy");
|
||||
for (AuthenticationExecutionInfoRepresentation e : executions) {
|
||||
if (e.getProviderId().equals("testsuite-client-dummy")) {
|
||||
e.setRequirement("ALTERNATIVE");
|
||||
testRealm().flows().updateExecutions("testsuite-client-dummy", e);
|
||||
}
|
||||
}
|
||||
flowConfigured = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicTest() {
|
||||
Review r = new Review().invoke()
|
||||
.assertSuccess();
|
||||
|
||||
String userId = testRealm().users().search(r.username).get(0).getId();
|
||||
|
||||
OpenShiftTokenReviewResponseRepresentation.User user = r.response.getStatus().getUser();
|
||||
|
||||
assertEquals(userId, user.getUid());
|
||||
assertEquals("test-user@localhost", user.getUsername());
|
||||
assertNotNull(user.getExtra());
|
||||
|
||||
r.assertScope("openid", "email", "profile");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void groups() {
|
||||
new Review().username("groups-user")
|
||||
.invoke()
|
||||
.assertSuccess().assertGroups("topGroup", "level2group");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customScopes() {
|
||||
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setProtocol("openid-connect");
|
||||
clientScope.setId("user:info");
|
||||
clientScope.setName("user:info");
|
||||
|
||||
testRealm().clientScopes().create(clientScope);
|
||||
|
||||
ClientRepresentation clientRep = testRealm().clients().findByClientId("test-app").get(0);
|
||||
|
||||
testRealm().clients().get(clientRep.getId()).addOptionalClientScope("user:info");
|
||||
|
||||
try {
|
||||
oauth.scope("user:info");
|
||||
new Review()
|
||||
.invoke()
|
||||
.assertSuccess().assertScope("openid", "user:info", "profile", "email");
|
||||
} finally {
|
||||
testRealm().clients().get(clientRep.getId()).removeOptionalClientScope("user:info");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyScope() {
|
||||
ClientRepresentation clientRep = testRealm().clients().findByClientId("test-app").get(0);
|
||||
|
||||
List<String> scopes = new LinkedList<>();
|
||||
for (ClientScopeRepresentation s : testRealm().clients().get(clientRep.getId()).getDefaultClientScopes()) {
|
||||
scopes.add(s.getId());
|
||||
}
|
||||
|
||||
for (String s : scopes) {
|
||||
testRealm().clients().get(clientRep.getId()).removeDefaultClientScope(s);
|
||||
}
|
||||
|
||||
oauth.openid(false);
|
||||
try {
|
||||
new Review()
|
||||
.invoke()
|
||||
.assertSuccess().assertEmptyScope();
|
||||
} finally {
|
||||
oauth.openid(true);
|
||||
|
||||
for (String s : scopes) {
|
||||
testRealm().clients().get(clientRep.getId()).addDefaultClientScope(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expiredToken() {
|
||||
try {
|
||||
new Review()
|
||||
.runAfterTokenRequest(i -> setTimeOffset(testRealm().toRepresentation().getAccessTokenLifespan() + 10))
|
||||
.invoke()
|
||||
.assertError(401, "Token verification failure");
|
||||
} finally {
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidPublicKey() {
|
||||
new Review()
|
||||
.runAfterTokenRequest(i -> {
|
||||
String header = i.token.split("\\.")[0];
|
||||
String s = new String(Base64Url.decode(header));
|
||||
s = s.replace(",\"kid\" : \"", ",\"kid\" : \"x");
|
||||
String newHeader = Base64Url.encode(s.getBytes());
|
||||
i.token = i.token.replaceFirst(header, newHeader);
|
||||
})
|
||||
.invoke()
|
||||
.assertError(401, "Invalid public key");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noUserSession() {
|
||||
new Review()
|
||||
.runAfterTokenRequest(i -> {
|
||||
String userId = testRealm().users().search(i.username).get(0).getId();
|
||||
testRealm().users().get(userId).logout();
|
||||
})
|
||||
.invoke()
|
||||
.assertError(401, "Token verification failure");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidTokenSignature() {
|
||||
new Review()
|
||||
.runAfterTokenRequest(i -> i.token += "x")
|
||||
.invoke()
|
||||
.assertError(401, "Token verification failure");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void realmDisabled() {
|
||||
RealmRepresentation r = testRealm().toRepresentation();
|
||||
try {
|
||||
new Review().runAfterTokenRequest(i -> {
|
||||
r.setEnabled(false);
|
||||
testRealm().update(r);
|
||||
}).invoke().assertError(401, null);
|
||||
|
||||
|
||||
} finally {
|
||||
r.setEnabled(true);
|
||||
testRealm().update(r);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicClientNotPermitted() {
|
||||
ClientRepresentation clientRep = testRealm().clients().findByClientId("test-app").get(0);
|
||||
clientRep.setPublicClient(true);
|
||||
testRealm().clients().get(clientRep.getId()).update(clientRep);
|
||||
try {
|
||||
new Review().invoke().assertError(401, "Public client is not permitted to invoke token review endpoint");
|
||||
} finally {
|
||||
clientRep.setPublicClient(false);
|
||||
testRealm().clients().get(clientRep.getId()).update(clientRep);
|
||||
}
|
||||
}
|
||||
|
||||
private class Review {
|
||||
|
||||
private String realm = "test";
|
||||
private String clientId = "test-app";
|
||||
private String username = "test-user@localhost";
|
||||
private String password = "password";
|
||||
private InvokeRunnable runAfterTokenRequest;
|
||||
|
||||
private String token;
|
||||
private int responseStatus;
|
||||
private OpenShiftTokenReviewResponseRepresentation response;
|
||||
|
||||
public Review username(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Review runAfterTokenRequest(InvokeRunnable runnable) {
|
||||
this.runAfterTokenRequest = runnable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Review invoke() {
|
||||
try {
|
||||
String userId = testRealm().users().search(username).get(0).getId();
|
||||
oauth.doLogin(username, password);
|
||||
EventRepresentation loginEvent = events.expectLogin().user(userId).assertEvent();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()).detail("client_auth_method", "testsuite-client-dummy").user(userId).assertEvent();
|
||||
|
||||
token = accessTokenResponse.getAccessToken();
|
||||
|
||||
if (runAfterTokenRequest != null) {
|
||||
runAfterTokenRequest.run(this);
|
||||
}
|
||||
|
||||
CloseableHttpClient client = HttpClientBuilder.create().build();
|
||||
|
||||
String url = AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/realms/" + realm +"/protocol/openid-connect/ext/openshift-token-review/" + clientId;
|
||||
|
||||
OpenShiftTokenReviewRequestRepresentation request = new OpenShiftTokenReviewRequestRepresentation();
|
||||
OpenShiftTokenReviewRequestRepresentation.Spec spec = new OpenShiftTokenReviewRequestRepresentation.Spec();
|
||||
spec.setToken(token);
|
||||
request.setSpec(spec);
|
||||
|
||||
SimpleHttp.Response r = SimpleHttp.doPost(url, client).json(request).asResponse();
|
||||
|
||||
responseStatus = r.getStatus();
|
||||
response = r.asJson(OpenShiftTokenReviewResponseRepresentation.class);
|
||||
|
||||
assertEquals("authentication.k8s.io/v1beta1", response.getApiVersion());
|
||||
assertEquals("TokenReview", response.getKind());
|
||||
|
||||
client.close();
|
||||
|
||||
return this;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Review assertSuccess() {
|
||||
assertEquals(200, responseStatus);
|
||||
assertTrue(response.getStatus().isAuthenticated());
|
||||
assertNotNull(response.getStatus().getUser());
|
||||
return this;
|
||||
}
|
||||
|
||||
private Review assertError(int expectedStatus, String expectedReason) {
|
||||
assertEquals(expectedStatus, responseStatus);
|
||||
assertFalse(response.getStatus().isAuthenticated());
|
||||
assertNull(response.getStatus().getUser());
|
||||
|
||||
if (expectedReason != null) {
|
||||
EventRepresentation poll = events.poll();
|
||||
assertEquals(expectedReason, poll.getDetails().get(Details.REASON));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void assertScope(String... expectedScope) {
|
||||
List<String> actualScopes = Arrays.asList(response.getStatus().getUser().getExtra().getScopes());
|
||||
assertEquals(expectedScope.length, actualScopes.size());
|
||||
assertThat(actualScopes, containsInAnyOrder(expectedScope));
|
||||
}
|
||||
|
||||
private void assertEmptyScope() {
|
||||
assertNull(response.getStatus().getUser().getExtra());
|
||||
}
|
||||
|
||||
private void assertGroups(String... expectedGroups) {
|
||||
List<String> actualGroups = new LinkedList<>(response.getStatus().getUser().getGroups());
|
||||
assertEquals(expectedGroups.length, actualGroups.size());
|
||||
assertThat(actualGroups, containsInAnyOrder(expectedGroups));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface InvokeRunnable {
|
||||
void run(Review i);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue