Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d41c8a45f | ||
|
|
5398f45b85 |
No files matched your search
+32
-19
@@ -31,25 +31,38 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>adapter-core</module>
|
||||
<module>as7-eap6</module>
|
||||
<module>installed</module>
|
||||
<module>fuse7</module>
|
||||
<module>kcinit</module>
|
||||
<module>jaxrs-oauth-client</module>
|
||||
<module>jetty</module>
|
||||
<module>js</module>
|
||||
<module>osgi-adapter</module>
|
||||
<module>servlet-filter</module>
|
||||
<module>servlet-oauth-client</module>
|
||||
<module>spring-boot</module>
|
||||
<module>spring-boot2</module>
|
||||
<module>spring-boot-adapter-core</module>
|
||||
<module>spring-boot-container-bundle</module>
|
||||
<module>spring-security</module>
|
||||
<module>tomcat</module>
|
||||
<module>undertow</module>
|
||||
<module>wildfly</module>
|
||||
<module>wildfly-elytron</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nonServer</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!serverOnly</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>adapter-core</module>
|
||||
<module>as7-eap6</module>
|
||||
<module>installed</module>
|
||||
<module>fuse7</module>
|
||||
<module>kcinit</module>
|
||||
<module>jaxrs-oauth-client</module>
|
||||
<module>jetty</module>
|
||||
<module>osgi-adapter</module>
|
||||
<module>servlet-filter</module>
|
||||
<module>servlet-oauth-client</module>
|
||||
<module>spring-boot</module>
|
||||
<module>spring-boot2</module>
|
||||
<module>spring-boot-adapter-core</module>
|
||||
<module>spring-boot-container-bundle</module>
|
||||
<module>spring-security</module>
|
||||
<module>tomcat</module>
|
||||
<module>undertow</module>
|
||||
<module>wildfly</module>
|
||||
<module>wildfly-elytron</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
+15
-2
@@ -31,8 +31,21 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>spi</module>
|
||||
<module>saml</module>
|
||||
<module>oidc</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nonServer</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!serverOnly</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>spi</module>
|
||||
<module>saml</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
+14
-1
@@ -19,6 +19,19 @@
|
||||
|
||||
<modules>
|
||||
<module>policy</module>
|
||||
<module>client</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nonServer</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!serverOnly</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>client</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
+13
-1
@@ -44,7 +44,6 @@
|
||||
</licenses>
|
||||
|
||||
<modules>
|
||||
<module>adapter</module>
|
||||
<module>spi</module>
|
||||
</modules>
|
||||
|
||||
@@ -77,5 +76,18 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>nonServer</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!serverOnly</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>adapter</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -31,7 +31,20 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>adapter-feature-pack</module>
|
||||
<module>server-feature-pack</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nonServer</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!serverOnly</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>adapter-feature-pack</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
+14
-3
@@ -36,16 +36,27 @@
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>adapters</module>
|
||||
<module>saml-adapters</module>
|
||||
<module>feature-packs</module>
|
||||
<module>licenses-common</module>
|
||||
<module>maven-plugins</module>
|
||||
<module>server-dist</module>
|
||||
<module>server-overlay</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nonServer</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!serverOnly</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>adapters</module>
|
||||
<module>saml-adapters</module>
|
||||
<module>server-overlay</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>jboss-release</id>
|
||||
<modules>
|
||||
|
||||
+15
-2
@@ -31,8 +31,21 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>admin-client</module>
|
||||
<module>client-registration</module>
|
||||
<module>client-cli</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nonServer</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!serverOnly</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>admin-client</module>
|
||||
<module>client-registration</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -197,13 +197,10 @@
|
||||
<module>services</module>
|
||||
<module>themes</module>
|
||||
<module>model</module>
|
||||
<module>util</module>
|
||||
<module>wildfly</module>
|
||||
<module>integration</module>
|
||||
<module>adapters</module>
|
||||
<module>authz</module>
|
||||
<module>examples</module>
|
||||
<module>misc</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -1605,9 +1602,6 @@
|
||||
<product.version>${project.version}</product.version>
|
||||
<product.default-profile>community</product.default-profile>
|
||||
</properties>
|
||||
<modules>
|
||||
<module>proxy</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
@@ -1633,7 +1627,7 @@
|
||||
<id>testsuite</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!skipTestsuite</name>
|
||||
<name>!skipTests</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
@@ -1641,6 +1635,20 @@
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>nonServer</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!serverOnly</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>examples</module>
|
||||
<module>misc</module>
|
||||
<module>util</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>distribution</id>
|
||||
<modules>
|
||||
|
||||
@@ -91,5 +91,5 @@ public interface Errors {
|
||||
String UNKNOWN_IDENTITY_PROVIDER = "unknown_identity_provider";
|
||||
String ILLEGAL_ORIGIN = "illegal_origin";
|
||||
String DISPLAY_UNSUPPORTED = "display_unsupported";
|
||||
|
||||
String INVALID_SCOPE = "invalid_scope";
|
||||
}
|
||||
+47
@@ -50,6 +50,8 @@ public class DefaultAuthenticationFlows {
|
||||
|
||||
public static final String IDP_REVIEW_PROFILE_CONFIG_ALIAS = "review profile config";
|
||||
public static final String IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS = "create unique user config";
|
||||
public static final String OPENSHIFT_CHALLENGE_FLOW = "openshift challenge flow";
|
||||
public static final String OPENSHIFT_CHALLENGE_FLOW_DESCRIPTION = "openshift flow for clients requiring challenge mode";
|
||||
|
||||
public static void addFlows(RealmModel realm) {
|
||||
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm);
|
||||
@@ -60,6 +62,7 @@ public class DefaultAuthenticationFlows {
|
||||
if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, false);
|
||||
if (realm.getFlowByAlias(SAML_ECP_FLOW) == null) samlEcpProfile(realm);
|
||||
if (realm.getFlowByAlias(DOCKER_AUTH) == null) dockerAuthenticationFlow(realm);
|
||||
if (realm.getFlowByAlias(OPENSHIFT_CHALLENGE_FLOW) == null) openshiftChallengeFlow(realm);
|
||||
}
|
||||
public static void migrateFlows(RealmModel realm) {
|
||||
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
|
||||
@@ -70,6 +73,7 @@ public class DefaultAuthenticationFlows {
|
||||
if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, true);
|
||||
if (realm.getFlowByAlias(SAML_ECP_FLOW) == null) samlEcpProfile(realm);
|
||||
if (realm.getFlowByAlias(DOCKER_AUTH) == null) dockerAuthenticationFlow(realm);
|
||||
if (realm.getFlowByAlias(OPENSHIFT_CHALLENGE_FLOW) == null) openshiftChallengeFlow(realm);
|
||||
}
|
||||
|
||||
public static void registrationFlow(RealmModel realm) {
|
||||
@@ -562,4 +566,47 @@ public class DefaultAuthenticationFlows {
|
||||
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
}
|
||||
|
||||
public static void openshiftChallengeFlow(RealmModel realm) {
|
||||
AuthenticationFlowModel challengeFlow = new AuthenticationFlowModel();
|
||||
challengeFlow.setAlias(OPENSHIFT_CHALLENGE_FLOW);
|
||||
challengeFlow.setDescription(OPENSHIFT_CHALLENGE_FLOW_DESCRIPTION);
|
||||
challengeFlow.setProviderId("basic-flow");
|
||||
challengeFlow.setTopLevel(true);
|
||||
challengeFlow.setBuiltIn(true);
|
||||
challengeFlow = realm.addAuthenticationFlow(challengeFlow);
|
||||
|
||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(challengeFlow.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setAuthenticator("no-cookie-redirect");
|
||||
execution.setPriority(10);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(challengeFlow.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setAuthenticator("basic-auth");
|
||||
execution.setPriority(20);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(challengeFlow.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
|
||||
execution.setAuthenticator("basic-auth-otp");
|
||||
execution.setPriority(30);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(challengeFlow.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
|
||||
execution.setAuthenticator("auth-spnego");
|
||||
execution.setPriority(40);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -135,5 +135,20 @@ public abstract class AbstractClientStorageAdapter extends UnsupportedOperations
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof ClientModel)) return false;
|
||||
|
||||
ClientModel that = (ClientModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+12
@@ -263,4 +263,16 @@ public abstract class AbstractReadOnlyClientStorageAdapter extends AbstractClien
|
||||
throw new ReadOnlyException("client is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayOnConsentScreen(boolean displayOnConsentScreen) {
|
||||
throw new ReadOnlyException("client is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConsentScreenText(String consentScreenText) {
|
||||
throw new ReadOnlyException("client is read only for this update");
|
||||
|
||||
}
|
||||
}
|
||||
+117
@@ -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.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedHashMap;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class BasicAuthAuthenticator extends AbstractUsernameFormAuthenticator implements Authenticator {
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
if (authorizationHeader != null) {
|
||||
String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
|
||||
String username = usernameSecret[0];
|
||||
String password = usernameSecret[1];
|
||||
MultivaluedMap<String, String> map = new MultivaluedHashMap<>();
|
||||
map.putSingle(AuthenticationManager.FORM_USERNAME, username);
|
||||
map.putSingle(CredentialRepresentation.PASSWORD, password);
|
||||
if (!validateUserAndPassword(context, map)) {
|
||||
return;
|
||||
}
|
||||
context.success();
|
||||
} else {
|
||||
Response response = challengeResponse(context);
|
||||
context.challenge(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected Response challengeResponse(AuthenticationFlowContext context) {
|
||||
return Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, getHeader(context)).build();
|
||||
}
|
||||
|
||||
private String getHeader(AuthenticationFlowContext context) {
|
||||
return "Basic realm=\"" + context.getRealm().getName() + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response invalidUser(AuthenticationFlowContext context) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response disabledUser(AuthenticationFlowContext context) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response invalidCredentials(AuthenticationFlowContext context) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class BasicAuthAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "basic-auth";
|
||||
public static final BasicAuthAuthenticator SINGLETON = new BasicAuthAuthenticator();
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return UserCredentialModel.PASSWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED
|
||||
};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Basic Auth Challenge";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Basic auth challenge.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedHashMap;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class BasicAuthOTPAuthenticator extends AbstractUsernameFormAuthenticator implements Authenticator {
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
if (authorizationHeader != null) {
|
||||
String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
|
||||
String username = usernameSecret[0];
|
||||
String password = usernameSecret[1];
|
||||
OTPPolicy otpPolicy = context.getRealm().getOTPPolicy();
|
||||
int otpLength = otpPolicy.getDigits();
|
||||
if (password.length() < otpLength) {
|
||||
Response response = challengeResponse(context);
|
||||
context.challenge(response);
|
||||
return;
|
||||
}
|
||||
String otp = password.substring(password.length() - otpLength);
|
||||
password = password.substring(0, password.length() - otpLength);
|
||||
MultivaluedMap<String, String> map = new MultivaluedHashMap<>();
|
||||
map.putSingle(AuthenticationManager.FORM_USERNAME, username);
|
||||
map.putSingle(CredentialRepresentation.PASSWORD, password);
|
||||
if (!validateUserAndPassword(context, map)) {
|
||||
return;
|
||||
}
|
||||
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(),
|
||||
UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp));
|
||||
if (!valid) {
|
||||
context.setUser(null);
|
||||
Response response = challengeResponse(context);
|
||||
context.challenge(response);
|
||||
} else {
|
||||
context.success();
|
||||
}
|
||||
} else {
|
||||
Response response = challengeResponse(context);
|
||||
context.challenge(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected Response challengeResponse(AuthenticationFlowContext context) {
|
||||
return Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, getHeader(context)).build();
|
||||
}
|
||||
|
||||
private String getHeader(AuthenticationFlowContext context) {
|
||||
return "Basic realm=\"" + context.getRealm().getName() + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response invalidUser(AuthenticationFlowContext context) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response disabledUser(AuthenticationFlowContext context) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response invalidCredentials(AuthenticationFlowContext context) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) {
|
||||
return challengeResponse(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return session.userCredentialManager().isConfiguredFor(realm, user, realm.getOTPPolicy().getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class BasicAuthOTPAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "basic-auth-otp";
|
||||
public static final BasicAuthOTPAuthenticator SINGLETON = new BasicAuthOTPAuthenticator();
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return UserCredentialModel.PASSWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED
|
||||
};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Basic Auth Password+OTP";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Basic auth challenge with OTP. Password param should contain a combination of password + otp. Realm's OTP policy is used to determine how to parse this. This SHOULD NOT BE USED in conjection with regular basic auth provider.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class NoCookieFlowRedirectAuthenticator implements Authenticator {
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
|
||||
if (!context.getHttpRequest().getUri().getQueryParameters().containsKey(LoginActionsService.AUTH_SESSION_ID)) {
|
||||
Response response = Response.status(302).header(HttpHeaders.LOCATION, context.getRefreshUrl(true)).build();
|
||||
context.challenge(response);
|
||||
return;
|
||||
}
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class NoCookieFlowRedirectAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "no-cookie-redirect";
|
||||
public static final NoCookieFlowRedirectAuthenticator SINGLETON = new NoCookieFlowRedirectAuthenticator();
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return UserCredentialModel.PASSWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED
|
||||
};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Browser Redirect/Refresh";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Perform a 302 redirect to get user agent's current URI on authenticate path with an auth_session_id query parameter. This is for client's that do not support cookies.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.kubernetes;
|
||||
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Kubernetes extends OIDCLoginProtocol {
|
||||
public Kubernetes(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
|
||||
super(session, realm, uriInfo, headers, event);
|
||||
}
|
||||
|
||||
public Kubernetes() {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.kubernetes;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientTemplateRepresentation;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KubernetesLoginProtocolFactory extends OIDCLoginProtocolFactory {
|
||||
private static final Logger logger = Logger.getLogger(KubernetesLoginProtocolFactory.class);
|
||||
public static final String LOGIN_PROTOCOL = "kubernetes";
|
||||
|
||||
@Override
|
||||
public LoginProtocol create(KeycloakSession session) {
|
||||
return new Kubernetes().setSession(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addDefaults(ClientModel client) {
|
||||
// OIDC will add defaults
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
|
||||
return new KubernetesProtocolEndpoint(realm, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return 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);
|
||||
|
||||
Set<String> origins = new HashSet<String>();
|
||||
String origin = UriUtils.getOrigin(root);
|
||||
logger.debugv("adding default client origin: {0}" , origin);
|
||||
origins.add(origin);
|
||||
newClient.setWebOrigins(origins);
|
||||
}
|
||||
if (rep.isBearerOnly() == null
|
||||
&& rep.isPublicClient() == null) {
|
||||
newClient.setPublicClient(true);
|
||||
}
|
||||
if (rep.isBearerOnly() == null) newClient.setBearerOnly(false);
|
||||
|
||||
// Backwards compatibility only
|
||||
if (rep.isDirectGrantsOnly() != null) {
|
||||
ServicesLogger.LOGGER.usingDeprecatedDirectGrantsOnly();
|
||||
newClient.setStandardFlowEnabled(!rep.isDirectGrantsOnly());
|
||||
newClient.setDirectAccessGrantsEnabled(rep.isDirectGrantsOnly());
|
||||
} else {
|
||||
if (rep.isStandardFlowEnabled() == null) newClient.setStandardFlowEnabled(true);
|
||||
if (rep.isDirectAccessGrantsEnabled() == null) newClient.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
if (rep.isImplicitFlowEnabled() == null) newClient.setImplicitFlowEnabled(true);
|
||||
if (rep.isPublicClient() == null) newClient.setPublicClient(true);
|
||||
if (rep.isFrontchannelLogout() == null) newClient.setFrontchannelLogout(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ProtocolMapperModel> getBuiltinMappers() {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createDefaultClientScopesImpl(RealmModel newRealm) {
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.kubernetes;
|
||||
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KubernetesProtocolEndpoint extends OIDCLoginProtocolService {
|
||||
public KubernetesProtocolEndpoint(RealmModel realm, EventBuilder event) {
|
||||
super(realm, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Token endpoint
|
||||
*/
|
||||
@Path("token")
|
||||
public Object token() {
|
||||
KubernetesTokenEndpoint endpoint = new KubernetesTokenEndpoint(tokenManager, realm, event);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public static UriBuilder tokenReviewnUrl(UriBuilder baseUriBuilder) {
|
||||
return tokenUrl(baseUriBuilder).path(KubernetesProtocolEndpoint.class, "tokenReview");
|
||||
}
|
||||
|
||||
public static UriBuilder tokenServiceBaseUrl(UriBuilder baseUriBuilder) {
|
||||
return baseUriBuilder.path(RealmsResource.class).path("{realm}/protocol/" + KubernetesLoginProtocolFactory.LOGIN_PROTOCOL);
|
||||
}
|
||||
|
||||
public static UriBuilder authUrl(UriInfo uriInfo) {
|
||||
UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
|
||||
return authUrl(baseUriBuilder);
|
||||
}
|
||||
|
||||
public static UriBuilder authUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "auth");
|
||||
}
|
||||
|
||||
public static UriBuilder tokenUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "token");
|
||||
}
|
||||
|
||||
public static UriBuilder userInfoUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "issueUserInfo");
|
||||
}
|
||||
|
||||
public static UriBuilder logoutUrl(UriInfo uriInfo) {
|
||||
UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
|
||||
return logoutUrl(baseUriBuilder);
|
||||
}
|
||||
|
||||
public static UriBuilder logoutUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "logout");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.kubernetes;
|
||||
|
||||
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.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.security.PublicKey;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KubernetesTokenEndpoint extends TokenEndpoint {
|
||||
public KubernetesTokenEndpoint(TokenManager tokenManager, RealmModel realm, EventBuilder event) {
|
||||
super(tokenManager, realm, event);
|
||||
}
|
||||
|
||||
@Path("token-review")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response tokenReview(TokenReviewRequestRepresentation reviewRequest) throws Exception {
|
||||
event.event(EventType.INTROSPECT_TOKEN);
|
||||
|
||||
checkSsl();
|
||||
checkRealm();
|
||||
|
||||
AccessToken toIntrospect = null;
|
||||
String token = reviewRequest.getSpec().getToken();
|
||||
try {
|
||||
RSATokenVerifier verifier = RSATokenVerifier.create(token)
|
||||
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
|
||||
|
||||
PublicKey publicKey = session.keys().getRsaPublicKey(realm, verifier.getHeader().getKeyId());
|
||||
if (publicKey == null) {
|
||||
event.detail(Details.REASON, "invalid public key");
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid token")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
} else {
|
||||
verifier.publicKey(publicKey);
|
||||
verifier.verify();
|
||||
toIntrospect = verifier.getToken();
|
||||
}
|
||||
} catch (VerificationException e) {
|
||||
event.detail(Details.REASON, "token verification failure");
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid token")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
}
|
||||
|
||||
if (!tokenManager.isTokenValid(session, realm, toIntrospect)) {
|
||||
event.detail(Details.REASON, "stale token");
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid token")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
}
|
||||
|
||||
|
||||
UserModel user = tokenManager.extractUser(session, realm, toIntrospect);
|
||||
|
||||
if (toIntrospect.getAudience() == null || toIntrospect.getAudience().length == 0) {
|
||||
event.detail(Details.REASON, "token audience is missing");
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid client")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
|
||||
}
|
||||
|
||||
ClientModel client = session.realms().getClientByClientId(toIntrospect.getIssuedFor(), realm);
|
||||
|
||||
|
||||
TokenReviewResponseRepresentation success = TokenReviewResponseRepresentation.success();
|
||||
TokenReviewResponseRepresentation.Status.User userRep = success.getStatus().getUser();
|
||||
userRep.setUid(user.getId());
|
||||
userRep.setUsername(user.getUsername());
|
||||
|
||||
|
||||
// todo should scope this information to avoid leaking info
|
||||
// should only display what is allowed for client
|
||||
for (GroupModel group : user.getGroups()) {
|
||||
addTree(userRep, group);
|
||||
}
|
||||
|
||||
event.success();
|
||||
return Response.ok(success, MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
public static void addTree(TokenReviewResponseRepresentation.Status.User user, GroupModel group) {
|
||||
if (group.getParentId() != null) {
|
||||
addTree(user, group.getParent());
|
||||
}
|
||||
|
||||
String groupPath = buildOsinGroupPath(group);
|
||||
if (!user.getGroups().contains(groupPath)) user.getGroups().add(groupPath);
|
||||
}
|
||||
|
||||
public static void buildOsinGroupPath(StringBuilder sb, GroupModel group) {
|
||||
if (group.getParent() != null) {
|
||||
buildOsinGroupPath(sb, group.getParent());
|
||||
sb.append(':');
|
||||
}
|
||||
sb.append(group.getName());
|
||||
}
|
||||
|
||||
public static String buildOsinGroupPath(GroupModel group) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
buildOsinGroupPath(sb, group);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.kubernetes;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TokenReviewRequestRepresentation implements Serializable {
|
||||
public static TokenReviewRequestRepresentation create(String token) {
|
||||
TokenReviewRequestRepresentation rep = new TokenReviewRequestRepresentation();
|
||||
rep.setSpec(new Spec());
|
||||
rep.getSpec().setToken(token);
|
||||
return rep;
|
||||
}
|
||||
@JsonProperty("apiVersion")
|
||||
protected String apiVersion = "authentication.k8s.io/v1beta1";
|
||||
@JsonProperty("kind")
|
||||
protected String kind = "TokenReview";
|
||||
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
public static class Spec implements Serializable {
|
||||
@JsonProperty("token")
|
||||
protected String token;
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a map of any other claims and data that might be in the request. Don't know what Openshift will add in future
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static TokenReviewRequestRepresentation build(String token) {
|
||||
TokenReviewRequestRepresentation request = new TokenReviewRequestRepresentation();
|
||||
request.setSpec(new Spec());
|
||||
request.getSpec().setToken(token);
|
||||
return request;
|
||||
}
|
||||
|
||||
@JsonProperty("spec")
|
||||
protected 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a map of any other claims and data that might be in the request. Don't know what Openshift will add in future
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
}
|
||||
Executable
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.kubernetes;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TokenReviewResponseRepresentation implements Serializable {
|
||||
@JsonProperty("apiVersion")
|
||||
protected String apiVersion = "authentication.k8s.io/v1beta1";
|
||||
@JsonProperty("kind")
|
||||
protected String kind = "TokenReview";
|
||||
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
public static class Status implements Serializable {
|
||||
@JsonProperty("authenticated")
|
||||
protected boolean authenticated;
|
||||
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
|
||||
public static class User implements Serializable {
|
||||
@JsonProperty("username")
|
||||
protected String username;
|
||||
@JsonProperty("uid")
|
||||
protected String uid;
|
||||
|
||||
@JsonProperty("groups")
|
||||
protected Set<String> groups = new HashSet<>();
|
||||
|
||||
public static class Extra implements Serializable {
|
||||
protected Map<String, Object> data = new HashMap<>();
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setData(String name, Object value) {
|
||||
data.put(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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 Set<String> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(Set<String> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public Extra getExtra() {
|
||||
return extra;
|
||||
}
|
||||
|
||||
public void setExtra(Extra extra) {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public void putExtra(String key, Object value) {
|
||||
if (extra == null) extra = new Extra();
|
||||
extra.data.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("user")
|
||||
protected User user;
|
||||
|
||||
@JsonProperty("error")
|
||||
protected String error;
|
||||
|
||||
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 String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("status")
|
||||
protected 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a map of any other claims and data that might be in the request. Don't know what Openshift will add in future
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
|
||||
public static TokenReviewResponseRepresentation error(String error) {
|
||||
TokenReviewResponseRepresentation response = new TokenReviewResponseRepresentation();
|
||||
response.getStatus().setError(error);
|
||||
return response;
|
||||
}
|
||||
public static TokenReviewResponseRepresentation success() {
|
||||
TokenReviewResponseRepresentation response = new TokenReviewResponseRepresentation();
|
||||
response.getStatus().setAuthenticated(true);
|
||||
response.getStatus().setUser(new Status.User());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -63,9 +63,9 @@ import java.util.List;
|
||||
*/
|
||||
public class OIDCLoginProtocolService {
|
||||
|
||||
private RealmModel realm;
|
||||
private TokenManager tokenManager;
|
||||
private EventBuilder event;
|
||||
protected RealmModel realm;
|
||||
protected TokenManager tokenManager;
|
||||
protected EventBuilder event;
|
||||
|
||||
@Context
|
||||
private UriInfo uriInfo;
|
||||
|
||||
@@ -302,6 +302,67 @@ public class TokenManager {
|
||||
return new RefreshResult(res, TokenUtil.TOKEN_TYPE_OFFLINE.equals(refreshToken.getType()));
|
||||
}
|
||||
|
||||
public UserModel extractUser(KeycloakSession session, RealmModel realm, AccessToken token) {
|
||||
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
||||
if (client == null || !client.isEnabled() || token.getIssuedAt() < client.getNotBefore()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionState(), false, client.getId());
|
||||
if (AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||
return userSession.getUser();
|
||||
}
|
||||
|
||||
userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionState(), true, client.getId());
|
||||
if (AuthenticationManager.isOfflineSessionValid(realm, userSession)) {
|
||||
return userSession.getUser();
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public String getRequiredOAuthScope(KeycloakSession session, RealmModel realm, AccessToken token) {
|
||||
UserSessionModel userSession = null;
|
||||
boolean offline = TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType());
|
||||
|
||||
ClientModel client = extractClient(realm, token);
|
||||
if (client == null) return null;
|
||||
|
||||
if (offline) {
|
||||
|
||||
UserSessionManager sessionManager = new UserSessionManager(session);
|
||||
userSession = sessionManager.findOfflineUserSession(realm, token.getSessionState());
|
||||
} else {
|
||||
// Find userSession regularly for online tokens
|
||||
userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||
}
|
||||
|
||||
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||
|
||||
// Can theoretically happen in cross-dc environment. Try to see if userSession with our client is available in remoteCache
|
||||
if (clientSession == null) {
|
||||
userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, userSession.getId(), offline, client.getId());
|
||||
if (userSession != null) {
|
||||
clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||
}
|
||||
}
|
||||
if (clientSession != null) {
|
||||
return clientSession.getNote(OAuth2Constants.SCOPE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ClientModel extractClient(RealmModel realm, AccessToken token) {
|
||||
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
||||
if (client == null || !client.isEnabled() || token.getIssuedAt() < client.getNotBefore()) {
|
||||
return null;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void validateTokenReuse(KeycloakSession session, RealmModel realm, RefreshToken refreshToken,
|
||||
TokenValidation validation) throws OAuthErrorException {
|
||||
if (realm.isRevokeRefreshToken()) {
|
||||
|
||||
@@ -115,9 +115,9 @@ import java.security.MessageDigest;
|
||||
public class TokenEndpoint {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(TokenEndpoint.class);
|
||||
private MultivaluedMap<String, String> formParams;
|
||||
private ClientModel client;
|
||||
private Map<String, String> clientAuthAttributes;
|
||||
protected MultivaluedMap<String, String> formParams;
|
||||
protected ClientModel client;
|
||||
protected Map<String, String> clientAuthAttributes;
|
||||
|
||||
private enum Action {
|
||||
AUTHORIZATION_CODE, REFRESH_TOKEN, PASSWORD, CLIENT_CREDENTIALS, TOKEN_EXCHANGE, PERMISSION
|
||||
@@ -127,32 +127,32 @@ public class TokenEndpoint {
|
||||
private static final Pattern VALID_CODE_VERIFIER_PATTERN = Pattern.compile("^[0-9a-zA-Z\\-\\.~_]+$");
|
||||
|
||||
@Context
|
||||
private KeycloakSession session;
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
private HttpRequest request;
|
||||
protected HttpRequest request;
|
||||
|
||||
@Context
|
||||
private HttpResponse httpResponse;
|
||||
protected HttpResponse httpResponse;
|
||||
|
||||
@Context
|
||||
private HttpHeaders headers;
|
||||
protected HttpHeaders headers;
|
||||
|
||||
@Context
|
||||
private UriInfo uriInfo;
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
@Context
|
||||
private ClientConnection clientConnection;
|
||||
protected ClientConnection clientConnection;
|
||||
|
||||
private final TokenManager tokenManager;
|
||||
private final RealmModel realm;
|
||||
private final EventBuilder event;
|
||||
protected final TokenManager tokenManager;
|
||||
protected final RealmModel realm;
|
||||
protected final EventBuilder event;
|
||||
|
||||
private Action action;
|
||||
protected Action action;
|
||||
|
||||
private String grantType;
|
||||
protected String grantType;
|
||||
|
||||
private Cors cors;
|
||||
protected Cors cors;
|
||||
|
||||
public TokenEndpoint(TokenManager tokenManager, RealmModel realm, EventBuilder event) {
|
||||
this.tokenManager = tokenManager;
|
||||
@@ -210,19 +210,19 @@ public class TokenEndpoint {
|
||||
return Cors.add(request, Response.ok()).auth().preflight().allowedMethods("POST", "OPTIONS").build();
|
||||
}
|
||||
|
||||
private void checkSsl() {
|
||||
protected void checkSsl() {
|
||||
if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
|
||||
throw new CorsErrorResponseException(cors.allowAllOrigins(), OAuthErrorException.INVALID_REQUEST, "HTTPS required", Response.Status.FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRealm() {
|
||||
protected void checkRealm() {
|
||||
if (!realm.isEnabled()) {
|
||||
throw new CorsErrorResponseException(cors.allowAllOrigins(), "access_denied", "Realm not enabled", Response.Status.FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkClient() {
|
||||
protected void checkClient() {
|
||||
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event);
|
||||
client = clientAuth.getClient();
|
||||
clientAuthAttributes = clientAuth.getClientAuthAttributes();
|
||||
@@ -236,7 +236,7 @@ public class TokenEndpoint {
|
||||
|
||||
}
|
||||
|
||||
private void checkGrantType() {
|
||||
protected void checkGrantType() {
|
||||
if (grantType == null) {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Missing form parameter: " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftLoginProtocolFactory extends OIDCLoginProtocolFactory{
|
||||
private static final Logger logger = Logger.getLogger(OpenshiftLoginProtocolFactory.class);
|
||||
public static final String LOGIN_PROTOCOL = "openshift";
|
||||
|
||||
@Override
|
||||
public Map<String, ProtocolMapperModel> getBuiltinMappers() {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createDefaultClientScopesImpl(RealmModel newRealm) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addDefaults(ClientModel client) {
|
||||
// OIDC will add defaults
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
|
||||
return new OpenshiftProtocolEndpoint(realm, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return 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);
|
||||
|
||||
Set<String> origins = new HashSet<String>();
|
||||
String origin = UriUtils.getOrigin(root);
|
||||
logger.debugv("adding default client origin: {0}" , origin);
|
||||
origins.add(origin);
|
||||
newClient.setWebOrigins(origins);
|
||||
}
|
||||
if (rep.isBearerOnly() == null
|
||||
&& rep.isPublicClient() == null) {
|
||||
newClient.setPublicClient(true);
|
||||
}
|
||||
if (rep.isBearerOnly() == null) newClient.setBearerOnly(false);
|
||||
|
||||
// Backwards compatibility only
|
||||
if (rep.isDirectGrantsOnly() != null) {
|
||||
ServicesLogger.LOGGER.usingDeprecatedDirectGrantsOnly();
|
||||
newClient.setStandardFlowEnabled(!rep.isDirectGrantsOnly());
|
||||
newClient.setDirectAccessGrantsEnabled(rep.isDirectGrantsOnly());
|
||||
} else {
|
||||
if (rep.isStandardFlowEnabled() == null) newClient.setStandardFlowEnabled(true);
|
||||
if (rep.isDirectAccessGrantsEnabled() == null) newClient.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
if (rep.isImplicitFlowEnabled() == null) newClient.setImplicitFlowEnabled(true);
|
||||
if (rep.isPublicClient() == null) newClient.setPublicClient(true);
|
||||
if (rep.isFrontchannelLogout() == null) newClient.setFrontchannelLogout(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftProtocolEndpoint extends OIDCLoginProtocolService {
|
||||
public OpenshiftProtocolEndpoint(RealmModel realm, EventBuilder event) {
|
||||
super(realm, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Token endpoint
|
||||
*/
|
||||
@Path("token")
|
||||
public Object token() {
|
||||
OpenshiftTokenEndpoint endpoint = new OpenshiftTokenEndpoint(tokenManager, realm, event);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public static UriBuilder tokenServiceBaseUrl(UriInfo uriInfo) {
|
||||
UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
|
||||
return tokenServiceBaseUrl(baseUriBuilder);
|
||||
}
|
||||
|
||||
public static UriBuilder tokenServiceBaseUrl(UriBuilder baseUriBuilder) {
|
||||
return baseUriBuilder.path(RealmsResource.class).path("{realm}/protocol/" + OpenshiftLoginProtocolFactory.LOGIN_PROTOCOL);
|
||||
}
|
||||
|
||||
public static UriBuilder authUrl(UriInfo uriInfo) {
|
||||
UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
|
||||
return authUrl(baseUriBuilder);
|
||||
}
|
||||
|
||||
public static UriBuilder authUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "auth");
|
||||
}
|
||||
|
||||
public static UriBuilder tokenUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "token");
|
||||
}
|
||||
|
||||
public static UriBuilder certsUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "certs");
|
||||
}
|
||||
|
||||
public static UriBuilder userInfoUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "issueUserInfo");
|
||||
}
|
||||
|
||||
public static UriBuilder tokenIntrospectionUrl(UriBuilder baseUriBuilder) {
|
||||
return tokenUrl(baseUriBuilder).path(TokenEndpoint.class, "introspect");
|
||||
}
|
||||
|
||||
public static UriBuilder tokenReviewnUrl(UriBuilder baseUriBuilder) {
|
||||
return tokenUrl(baseUriBuilder).path(OpenshiftTokenEndpoint.class, "tokenReview");
|
||||
}
|
||||
|
||||
public static UriBuilder logoutUrl(UriInfo uriInfo) {
|
||||
UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
|
||||
return logoutUrl(baseUriBuilder);
|
||||
}
|
||||
|
||||
public static UriBuilder logoutUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "logout");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
||||
import org.keycloak.protocol.openshift.clientstorage.OpenshiftClientModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftTokenEndpoint extends TokenEndpoint {
|
||||
public OpenshiftTokenEndpoint(TokenManager tokenManager, RealmModel realm, EventBuilder event) {
|
||||
super(tokenManager, realm, event);
|
||||
}
|
||||
|
||||
@Path("token-review")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response tokenReview(TokenReviewRequestRepresentation reviewRequest) throws Exception {
|
||||
event.event(EventType.INTROSPECT_TOKEN);
|
||||
|
||||
checkSsl();
|
||||
checkRealm();
|
||||
|
||||
AccessToken toIntrospect = null;
|
||||
String token = reviewRequest.getSpec().getToken();
|
||||
try {
|
||||
RSATokenVerifier verifier = RSATokenVerifier.create(token)
|
||||
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
|
||||
|
||||
PublicKey publicKey = session.keys().getRsaPublicKey(realm, verifier.getHeader().getKeyId());
|
||||
if (publicKey == null) {
|
||||
event.detail(Details.REASON, "invalid public key");
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid token")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
} else {
|
||||
verifier.publicKey(publicKey);
|
||||
verifier.verify();
|
||||
toIntrospect = verifier.getToken();
|
||||
}
|
||||
} catch (VerificationException e) {
|
||||
event.detail(Details.REASON, "token verification failure");
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid token")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
}
|
||||
|
||||
if (!tokenManager.isTokenValid(session, realm, toIntrospect)) {
|
||||
event.detail(Details.REASON, "stale token");
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid token")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
}
|
||||
|
||||
|
||||
UserModel user = tokenManager.extractUser(session, realm, toIntrospect);
|
||||
|
||||
if (toIntrospect.getAudience() == null || toIntrospect.getAudience().length == 0) {
|
||||
event.detail(Details.REASON, "token audience is missing");
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid client")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
|
||||
}
|
||||
|
||||
ClientModel client = session.realms().getClientByClientId(toIntrospect.getAudience()[0], realm);
|
||||
|
||||
if (client == null) {
|
||||
event.detail(Details.REASON, "invalid audience");
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error("invalid client")).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
|
||||
}
|
||||
|
||||
TokenReviewResponseRepresentation success = TokenReviewResponseRepresentation.success();
|
||||
TokenReviewResponseRepresentation.Status.User userRep = success.getStatus().getUser();
|
||||
userRep.setUid(user.getId());
|
||||
userRep.setUsername(user.getUsername());
|
||||
|
||||
String scopeParam = tokenManager.getRequiredOAuthScope(session, realm, toIntrospect);
|
||||
List<String> scopes = new LinkedList<>();
|
||||
if (scopeParam != null) {
|
||||
for (String scope : scopeParam.split("\\s+")) {
|
||||
scopes.add(scope);
|
||||
}
|
||||
}
|
||||
if (client instanceof OpenshiftClientModel) {
|
||||
Set<String> failedScopes = ((OpenshiftClientModel)client).validateRequestedScope(scopes);
|
||||
if (failedScopes != null && !failedScopes.isEmpty()) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String failed : failedScopes) builder.append(" ").append(failed);
|
||||
event.detail(Details.REASON, builder.toString());
|
||||
event.error(Errors.INVALID_SCOPE);
|
||||
return Response.status(401).entity(TokenReviewResponseRepresentation.error(Errors.INVALID_SCOPE)).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||
|
||||
}
|
||||
if (!scopes.isEmpty()) userRep.putExtra("scopes.authorization.openshift.io", scopes);
|
||||
}
|
||||
|
||||
// todo should scope this information to avoid leaking info
|
||||
// should only display what is allowed for client
|
||||
for (GroupModel group : user.getGroups()) {
|
||||
addTree(userRep, group);
|
||||
}
|
||||
|
||||
event.success();
|
||||
return Response.ok(success, MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
public static void addTree(TokenReviewResponseRepresentation.Status.User user, GroupModel group) {
|
||||
if (group.getParentId() != null) {
|
||||
addTree(user, group.getParent());
|
||||
}
|
||||
|
||||
String groupPath = buildOsinGroupPath(group);
|
||||
if (!user.getGroups().contains(groupPath)) user.getGroups().add(groupPath);
|
||||
}
|
||||
|
||||
public static void buildOsinGroupPath(StringBuilder sb, GroupModel group) {
|
||||
if (group.getParent() != null) {
|
||||
buildOsinGroupPath(sb, group.getParent());
|
||||
sb.append(':');
|
||||
}
|
||||
sb.append(group.getName());
|
||||
}
|
||||
|
||||
public static String buildOsinGroupPath(GroupModel group) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
buildOsinGroupPath(sb, group);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.json.StringOrArrayDeserializer;
|
||||
import org.keycloak.json.StringOrArraySerializer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TokenReviewRequestRepresentation implements Serializable {
|
||||
public static TokenReviewRequestRepresentation create(String token) {
|
||||
TokenReviewRequestRepresentation rep = new TokenReviewRequestRepresentation();
|
||||
rep.setSpec(new Spec());
|
||||
rep.getSpec().setToken(token);
|
||||
return rep;
|
||||
}
|
||||
@JsonProperty("apiVersion")
|
||||
protected String apiVersion = "authentication.k8s.io/v1beta1";
|
||||
@JsonProperty("kind")
|
||||
protected String kind = "TokenReview";
|
||||
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
public static class Spec implements Serializable {
|
||||
@JsonProperty("token")
|
||||
protected String token;
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a map of any other claims and data that might be in the request. Don't know what Openshift will add in future
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static TokenReviewRequestRepresentation build(String token) {
|
||||
TokenReviewRequestRepresentation request = new TokenReviewRequestRepresentation();
|
||||
request.setSpec(new Spec());
|
||||
request.getSpec().setToken(token);
|
||||
return request;
|
||||
}
|
||||
|
||||
@JsonProperty("spec")
|
||||
protected 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a map of any other claims and data that might be in the request. Don't know what Openshift will add in future
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
}
|
||||
Executable
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TokenReviewResponseRepresentation implements Serializable {
|
||||
@JsonProperty("apiVersion")
|
||||
protected String apiVersion = "authentication.k8s.io/v1beta1";
|
||||
@JsonProperty("kind")
|
||||
protected String kind = "TokenReview";
|
||||
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
public static class Status implements Serializable {
|
||||
@JsonProperty("authenticated")
|
||||
protected boolean authenticated;
|
||||
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
|
||||
public static class User implements Serializable {
|
||||
@JsonProperty("username")
|
||||
protected String username;
|
||||
@JsonProperty("uid")
|
||||
protected String uid;
|
||||
|
||||
@JsonProperty("groups")
|
||||
protected Set<String> groups = new HashSet<>();
|
||||
|
||||
public static class Extra implements Serializable {
|
||||
protected Map<String, Object> data = new HashMap<>();
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setData(String name, Object value) {
|
||||
data.put(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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 Set<String> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(Set<String> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public Extra getExtra() {
|
||||
return extra;
|
||||
}
|
||||
|
||||
public void setExtra(Extra extra) {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public void putExtra(String key, Object value) {
|
||||
if (extra == null) extra = new Extra();
|
||||
extra.data.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("user")
|
||||
protected User user;
|
||||
|
||||
@JsonProperty("error")
|
||||
protected String error;
|
||||
|
||||
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 String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("status")
|
||||
protected 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a map of any other claims and data that might be in the request. Don't know what Openshift will add in future
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
|
||||
public static TokenReviewResponseRepresentation error(String error) {
|
||||
TokenReviewResponseRepresentation response = new TokenReviewResponseRepresentation();
|
||||
response.getStatus().setError(error);
|
||||
return response;
|
||||
}
|
||||
public static TokenReviewResponseRepresentation success() {
|
||||
TokenReviewResponseRepresentation response = new TokenReviewResponseRepresentation();
|
||||
response.getStatus().setAuthenticated(true);
|
||||
response.getStatus().setUser(new Status.User());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.clientstorage;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface OpenshiftClientModel extends ClientModel {
|
||||
Set<String> validateRequestedScope(List<String> requestedScopes);
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.clientstorage;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.storage.client.ClientStorageProviderModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftClientStorageModel extends ClientStorageProviderModel {
|
||||
protected transient String token;
|
||||
protected transient String openshiftUri;
|
||||
|
||||
public OpenshiftClientStorageModel() {
|
||||
}
|
||||
|
||||
public OpenshiftClientStorageModel(ComponentModel copy) {
|
||||
super(copy);
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
if (token == null) {
|
||||
token = getConfig().getFirst(OpenshiftClientStorageProviderFactory.ACCESS_TOKEN);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
getConfig().putSingle(OpenshiftClientStorageProviderFactory.ACCESS_TOKEN, token);
|
||||
}
|
||||
|
||||
public String getOpenshiftUri() {
|
||||
if (openshiftUri == null) {
|
||||
openshiftUri = getConfig().getFirst(OpenshiftClientStorageProviderFactory.OPENSHIFT_URI);
|
||||
}
|
||||
return openshiftUri;
|
||||
}
|
||||
|
||||
public void setOpenshiftUri(String openshiftUri) {
|
||||
this.openshiftUri = openshiftUri;
|
||||
getConfig().putSingle(OpenshiftClientStorageProviderFactory.OPENSHIFT_URI, openshiftUri);
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.clientstorage;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.openshift.connections.rest.OpenshiftClient;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.ServiceAccounts;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.oauth.OAuthClients;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.client.ClientLookupProvider;
|
||||
import org.keycloak.storage.client.ClientStorageProvider;
|
||||
import org.keycloak.storage.client.ClientStorageProviderModel;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftClientStorageProvider implements ClientStorageProvider, ClientLookupProvider {
|
||||
protected KeycloakSession session;
|
||||
protected OpenshiftClientStorageModel component;
|
||||
protected OpenshiftClient openshiftClient;
|
||||
|
||||
public OpenshiftClientStorageProvider(KeycloakSession session, ClientStorageProviderModel component) {
|
||||
this.session = session;
|
||||
this.component = new OpenshiftClientStorageModel(component);
|
||||
}
|
||||
|
||||
public OpenshiftClientStorageModel getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (openshiftClient != null) openshiftClient.close();
|
||||
|
||||
}
|
||||
|
||||
protected void initClient() {
|
||||
if (openshiftClient != null) return;
|
||||
openshiftClient = OpenshiftClient.instance(component.getOpenshiftUri(), component.getToken());
|
||||
|
||||
}
|
||||
|
||||
public OpenshiftClient getOpenshiftClient() {
|
||||
initClient();
|
||||
return openshiftClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
if (!storageId.getProviderId().equals(component.getId())) return null;
|
||||
String clientId = storageId.getExternalId();
|
||||
return getClientByClientId(clientId, realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientByClientId(String clientId, RealmModel realm) {
|
||||
initClient();
|
||||
clientId = clientId.trim();
|
||||
Matcher matcher = OpenshiftSAClientAdapter.SERVICE_ACCOUNT_PATTERN.matcher(clientId);
|
||||
if (matcher.matches()) {
|
||||
String namespace = matcher.group(1);
|
||||
String name = matcher.group(2);
|
||||
try {
|
||||
ServiceAccounts.ServiceAccountRepresentation rep = openshiftClient.api().namespace(namespace).serviceAccounts().get(name);
|
||||
return new OpenshiftSAClientAdapter(session, realm, clientId, rep, this);
|
||||
} catch (NotFoundException nfe) {
|
||||
if (nfe.getResponse() != null) nfe.getResponse().close();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
OAuthClients.OAuthClientRepresentation client = null;
|
||||
try {
|
||||
client = openshiftClient.apis().oauth().clients().get(clientId);
|
||||
return new OpenshiftOAuthClientAdapter(session, realm, component, client);
|
||||
} catch (NotFoundException nfe) {
|
||||
if (nfe.getResponse() != null) nfe.getResponse().close();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.clientstorage;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.storage.CacheableStorageProviderModel;
|
||||
import org.keycloak.storage.client.ClientStorageProviderFactory;
|
||||
import org.keycloak.storage.client.ClientStorageProviderModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.keycloak.storage.CacheableStorageProviderModel.CACHE_POLICY;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftClientStorageProviderFactory implements ClientStorageProviderFactory<OpenshiftClientStorageProvider> {
|
||||
@Override
|
||||
public OpenshiftClientStorageProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new OpenshiftClientStorageProvider(session, new ClientStorageProviderModel(model));
|
||||
}
|
||||
|
||||
|
||||
public static final String PROVIDER_ID = "openshift-oauth-clients";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Openshift OAuth Client Adapter";
|
||||
}
|
||||
|
||||
protected static final List<ProviderConfigProperty> CONFIG_PROPERTIES;
|
||||
|
||||
public static final String ACCESS_TOKEN = "access_token";
|
||||
public static final String OPENSHIFT_URI = "openshift_uri";
|
||||
|
||||
static {
|
||||
CONFIG_PROPERTIES = ProviderConfigurationBuilder.create()
|
||||
.property().name(ACCESS_TOKEN)
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.label("Access Token")
|
||||
.helpText("Bearer token that will be used to invoke on Openshift api server. Must have privilege to lookup oauth clients, service accounts, and invoke on token review interface")
|
||||
.add()
|
||||
.property().name(OPENSHIFT_URI)
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.label("Openshift URL")
|
||||
.helpText("Openshift api server URL base endpoint.")
|
||||
.add()
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return CONFIG_PROPERTIES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
|
||||
config.getConfig().putSingle(CACHE_POLICY, CacheableStorageProviderModel.CachePolicy.NO_CACHE.name());
|
||||
}
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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.clientstorage;
|
||||
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.oauth.OAuthClients;
|
||||
import org.keycloak.storage.client.AbstractReadOnlyClientStorageAdapter;
|
||||
import org.keycloak.storage.client.ClientStorageProviderModel;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import static org.keycloak.protocol.openshift.clientstorage.OpenshiftSAClientAdapter.ROLE_SCOPE_PATTERN;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftOAuthClientAdapter extends AbstractReadOnlyClientStorageAdapter implements OpenshiftClientModel {
|
||||
protected OAuthClients.OAuthClientRepresentation client;
|
||||
|
||||
public OpenshiftOAuthClientAdapter(KeycloakSession session, RealmModel realm, ClientStorageProviderModel component, OAuthClients.OAuthClientRepresentation client) {
|
||||
super(session, realm, component);
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return client.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return client.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Set<String> origins = new HashSet<>();
|
||||
static {
|
||||
origins.add("+");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getWebOrigins() {
|
||||
return origins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRedirectUris() {
|
||||
if (client.getRedirectURIs() != null) {
|
||||
Set<String> newSet = new HashSet<>();
|
||||
for (String uri : client.getRedirectURIs()) {
|
||||
if (!uri.endsWith("/")) uri += "/";
|
||||
uri += "*";
|
||||
newSet.add(uri);
|
||||
}
|
||||
return newSet;
|
||||
}
|
||||
|
||||
return client.getRedirectURIs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManagementUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBearerOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNodeReRegistrationTimeout() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientAuthenticatorType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateSecret(String secret) {
|
||||
return client.getSecret().equals(secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSecret() {
|
||||
return client.getSecret();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRegistrationToken() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return OIDCLoginProtocol.LOGIN_PROTOCOL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthenticationFlowBindingOverride(String binding) {
|
||||
if (!AuthenticationFlowBindings.BROWSER_BINDING.equals(binding)) return null;
|
||||
if (client.isRespondWithChallenges()) {
|
||||
AuthenticationFlowModel model = realm.getFlowByAlias(DefaultAuthenticationFlows.OPENSHIFT_CHALLENGE_FLOW);
|
||||
if (model != null) return model.getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||
if (client.isRespondWithChallenges()) {
|
||||
AuthenticationFlowModel model = realm.getFlowByAlias(DefaultAuthenticationFlows.OPENSHIFT_CHALLENGE_FLOW);
|
||||
if (model != null) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put(AuthenticationFlowBindings.BROWSER_BINDING, model.getId());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFrontchannelLogout() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublicClient() {
|
||||
return client.getSecret() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsentRequired() {
|
||||
return client.getGrantMethod() == null ? false : client.getGrantMethod().equalsIgnoreCase("prompt");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStandardFlowEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImplicitFlowEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectAccessGrantsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNotBefore() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ProtocolMapperModel> getProtocolMappers() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullScopeAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getScopeMappings() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmScopeMappings() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScope(RoleModel role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> validateRequestedScope(List<String> requestedScopes) {
|
||||
Set<String> failed = new HashSet<>();
|
||||
for (String requested : requestedScopes) {
|
||||
if (requested.equals("openid")) continue;
|
||||
if (client.getLiteralScopeRestrictions() != null && client.getLiteralScopeRestrictions().contains(requested)) continue;
|
||||
if (client.getClusterRoleRestrictions() == null || client.getClusterRoleRestrictions().isEmpty()) {
|
||||
failed.add(requested);
|
||||
continue;
|
||||
}
|
||||
Matcher m = ROLE_SCOPE_PATTERN.matcher(requested);
|
||||
if (!m.matches()) {
|
||||
failed.add(requested);
|
||||
continue;
|
||||
}
|
||||
boolean found = false;
|
||||
String role = m.group(1);
|
||||
String namespace = m.group(2);
|
||||
String escalationString = m.group(3);
|
||||
boolean escalation = escalationString != null && escalationString.trim().equals(":!");
|
||||
for (OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction restriction : client.getClusterRoleRestrictions()) {
|
||||
if (restriction.getNamespaces().contains("*") || restriction.getNamespaces().contains(namespace)) {
|
||||
if (restriction.getRoleNames().contains("*") || restriction.getRoleNames().contains(role)) {
|
||||
if (!escalation || restriction.isAllowEscalation()) {
|
||||
found = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) failed.add(requested);
|
||||
}
|
||||
return failed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayOnConsentScreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConsentScreenText() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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.clientstorage;
|
||||
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.openshift.TokenReviewRequestRepresentation;
|
||||
import org.keycloak.protocol.openshift.TokenReviewResponseRepresentation;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.ServiceAccounts;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.oauth.OAuthClients;
|
||||
import org.keycloak.storage.client.AbstractReadOnlyClientStorageAdapter;
|
||||
import org.keycloak.storage.client.ClientStorageProviderModel;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftSAClientAdapter extends AbstractReadOnlyClientStorageAdapter implements OpenshiftClientModel {
|
||||
public static final Pattern ROLE_SCOPE_PATTERN = Pattern.compile("role:([^:]+):([^:!]+)(:[!])?");
|
||||
public static final Pattern SERVICE_ACCOUNT_PATTERN = Pattern.compile("system:serviceaccount:([^:]+):([^:]+)");
|
||||
protected final ServiceAccounts.ServiceAccountRepresentation serviceAccount;
|
||||
protected final OpenshiftClientStorageProvider provider;
|
||||
protected final String clientId;
|
||||
|
||||
public static final Set<String> ALLOWED_SCOPES = new HashSet<>();
|
||||
|
||||
static {
|
||||
ALLOWED_SCOPES.add("user:info");
|
||||
ALLOWED_SCOPES.add("user:check-access");
|
||||
}
|
||||
|
||||
public OpenshiftSAClientAdapter(KeycloakSession session, RealmModel realm, String clientId, ServiceAccounts.ServiceAccountRepresentation serviceAccount, OpenshiftClientStorageProvider provider) {
|
||||
super(session, realm, provider.getComponent());
|
||||
this.serviceAccount = serviceAccount;
|
||||
this.provider = provider;
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getWebOrigins() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRedirectUris() {
|
||||
return serviceAccount.getOauthRedirectUris();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManagementUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBearerOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNodeReRegistrationTimeout() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientAuthenticatorType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateSecret(String secret) {
|
||||
TokenReviewRequestRepresentation request = TokenReviewRequestRepresentation.create(secret);
|
||||
Response response = provider.getOpenshiftClient().apis().kubernetesAuthentication().tokenReview().review(request);
|
||||
if (response.getStatus() < 200 || response.getStatus() > 204) {
|
||||
return false;
|
||||
}
|
||||
TokenReviewResponseRepresentation review = response.readEntity(TokenReviewResponseRepresentation.class);
|
||||
response.close();
|
||||
if (!review.getStatus().isAuthenticated()) return false;
|
||||
return clientId.equals(review.getStatus().getUser().getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSecret() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRegistrationToken() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return OIDCLoginProtocol.LOGIN_PROTOCOL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthenticationFlowBindingOverride(String binding) {
|
||||
if (!AuthenticationFlowBindings.BROWSER_BINDING.equals(binding)) return null;
|
||||
if (serviceAccount.oauthWantChallenges()) {
|
||||
AuthenticationFlowModel model = realm.getFlowByAlias(DefaultAuthenticationFlows.OPENSHIFT_CHALLENGE_FLOW);
|
||||
if (model != null) return model.getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||
if (serviceAccount.oauthWantChallenges()) {
|
||||
AuthenticationFlowModel model = realm.getFlowByAlias(DefaultAuthenticationFlows.OPENSHIFT_CHALLENGE_FLOW);
|
||||
if (model != null) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put(AuthenticationFlowBindings.BROWSER_BINDING, model.getId());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
@Override
|
||||
public boolean isFrontchannelLogout() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublicClient() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsentRequired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStandardFlowEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImplicitFlowEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectAccessGrantsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNotBefore() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ProtocolMapperModel> getProtocolMappers() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullScopeAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getScopeMappings() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmScopeMappings() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScope(RoleModel role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Set<String> validateRequestedScope(List<String> requestedScopes) {
|
||||
Set<String> failed = new HashSet<>();
|
||||
for (String requested : requestedScopes) {
|
||||
if (ALLOWED_SCOPES.contains(requested)) continue;
|
||||
|
||||
Matcher m = ROLE_SCOPE_PATTERN.matcher(requested);
|
||||
if (!m.matches()) {
|
||||
failed.add(requested);
|
||||
continue;
|
||||
}
|
||||
// I think that it has to match namespace? Not sure.
|
||||
String namespace = m.group(2);
|
||||
if (!serviceAccount.getNamespace().equals(namespace)) {
|
||||
failed.add(requested);
|
||||
}
|
||||
}
|
||||
return failed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayOnConsentScreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConsentScreenText() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.connections.rest;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class BaseRepresentation {
|
||||
@JsonProperty("apiVersion")
|
||||
protected String apiVersion;
|
||||
@JsonProperty("kind")
|
||||
protected String kind;
|
||||
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
public void setOtherClaims(Map<String, Object> otherClaims) {
|
||||
this.otherClaims = otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.connections.rest;
|
||||
|
||||
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import javax.ws.rs.client.ClientRequestFilter;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author rodrigo.sasaki@icarros.com.br
|
||||
*/
|
||||
public class BearerAuthFilter implements ClientRequestFilter {
|
||||
|
||||
public static final String AUTH_HEADER_PREFIX = "Bearer ";
|
||||
private final String tokenString;
|
||||
|
||||
public BearerAuthFilter(String tokenString) {
|
||||
this.tokenString = tokenString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, AUTH_HEADER_PREFIX + tokenString);
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.connections.rest;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class MetadataRepresentation {
|
||||
@JsonProperty("name")
|
||||
protected String name;
|
||||
@JsonProperty("namespace")
|
||||
protected String namespace;
|
||||
@JsonProperty("annotations")
|
||||
protected Map<String, String> annotations = new HashMap<>();
|
||||
|
||||
protected Map<String, Object> otherClaims = new HashMap<>();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public Map<String, String> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void setAnnotations(Map<String, String> annotations) {
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
public void setOtherClaims(Map<String, Object> otherClaims) {
|
||||
this.otherClaims = otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getOtherClaims() {
|
||||
return otherClaims;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setOtherClaims(String name, Object value) {
|
||||
otherClaims.put(name, value);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.connections.rest;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.Api;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.Apis;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OpenshiftClient {
|
||||
protected String token;
|
||||
protected String baseUrl;
|
||||
protected ResteasyClient httpClient;
|
||||
private final ResteasyWebTarget target;
|
||||
private final ResteasyWebTarget api;
|
||||
private final ResteasyWebTarget apis;
|
||||
|
||||
|
||||
protected OpenshiftClient(String baseUrl, String token, ResteasyClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
this.token = token;
|
||||
this.baseUrl = baseUrl;
|
||||
target = httpClient.target(baseUrl);
|
||||
target.register(new BearerAuthFilter(token));
|
||||
api = target.path("api");
|
||||
apis = target.path("apis");
|
||||
}
|
||||
|
||||
public static OpenshiftClient instance(String baseUrl, String token) {
|
||||
return new OpenshiftClient(baseUrl, token, new ResteasyClientBuilder().connectionPoolSize(1).disableTrustManager().build());
|
||||
}
|
||||
|
||||
public Api api() {
|
||||
return api.proxy(Api.class);
|
||||
}
|
||||
public Apis apis() {
|
||||
return apis.proxy(Apis.class);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
httpClient.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.connections.rest.api;
|
||||
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.Namespace;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface Api {
|
||||
@Path("v1/namespaces/{namespace}")
|
||||
Namespace namespace(@PathParam("namespace") String namespace);
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.connections.rest.api.v1;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface Namespace {
|
||||
@Path("serviceaccounts")
|
||||
ServiceAccounts serviceAccounts();
|
||||
@Path("secrets")
|
||||
Secrets secrets();
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.connections.rest.api.v1;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.protocol.openshift.connections.rest.BaseRepresentation;
|
||||
import org.keycloak.protocol.openshift.connections.rest.MetadataRepresentation;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
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.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface Secrets {
|
||||
public final String SERVICE_ACCOUNT_TOKEN="kubernetes.io/service-account-token";
|
||||
|
||||
class SecretRepresentation extends BaseRepresentation {
|
||||
@JsonProperty("metadata")
|
||||
protected MetadataRepresentation metadata = new MetadataRepresentation();
|
||||
|
||||
@JsonProperty("type")
|
||||
protected String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isServiceAccountToken() {
|
||||
return SERVICE_ACCOUNT_TOKEN.equals(type);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getToken() {
|
||||
Object data = otherClaims.get("data");
|
||||
if (data == null) return null;
|
||||
Map<String, Object> dataMap = (Map<String, Object>)data;
|
||||
Object token = dataMap.get("token");
|
||||
if (token == null) return null;
|
||||
try {
|
||||
byte[] decoded = Base64.decode((String)token);
|
||||
return new String(decoded, "UTF-8");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{secret}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public SecretRepresentation get(@PathParam("secret") String secret);
|
||||
|
||||
@GET
|
||||
@Path("{secret}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public String getPretty(@PathParam("secret") String secret, @QueryParam("pretty") boolean pretty);
|
||||
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.connections.rest.api.v1;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.keycloak.protocol.openshift.connections.rest.BaseRepresentation;
|
||||
import org.keycloak.protocol.openshift.connections.rest.MetadataRepresentation;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
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.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ServiceAccounts {
|
||||
|
||||
String SERVICEACCOUNTS_OPENSHIFT_IO_OAUTH_REDIRECTURI = "serviceaccounts.openshift.io/oauth-redirecturi";
|
||||
String SERVICEACCOUNTS_OPENSHIFT_IO_OAUTH_WANT_CHALLENGES = "serviceaccounts.openshift.io/oauth-want-challenges";
|
||||
|
||||
class ServiceAccountRepresentation extends BaseRepresentation {
|
||||
|
||||
public ServiceAccountRepresentation() {
|
||||
this.kind = "ServiceAccount";
|
||||
this.apiVersion = "v1";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@JsonProperty("metadata")
|
||||
protected MetadataRepresentation metadata = new MetadataRepresentation();
|
||||
|
||||
|
||||
public MetadataRepresentation getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(MetadataRepresentation metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
|
||||
@JsonProperty("secrets")
|
||||
protected List<Map<String, String>> secrets;
|
||||
|
||||
@JsonIgnore
|
||||
public Set<String> getSecrets() {
|
||||
if (secrets == null || secrets.isEmpty()) return Collections.EMPTY_SET;
|
||||
Set<String> set = new HashSet<>();
|
||||
for (Map<String, String> secret : secrets) {
|
||||
set.add(secret.values().iterator().next());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
|
||||
@JsonIgnore
|
||||
public String getName() {
|
||||
return metadata.getName();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setName(String name) {
|
||||
metadata.setName(name);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getNamespace() {
|
||||
return metadata.getNamespace();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setNamespace(String name) {
|
||||
metadata.setNamespace(name);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Map<String, String> getAnnotations() {
|
||||
return metadata.getAnnotations();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Set<String> getOauthRedirectUris() {
|
||||
Set<String> redirects = new HashSet<>();
|
||||
for (String key : getAnnotations().keySet()) {
|
||||
if (key.startsWith(SERVICEACCOUNTS_OPENSHIFT_IO_OAUTH_REDIRECTURI)) {
|
||||
redirects.add(getAnnotations().get(key));
|
||||
}
|
||||
}
|
||||
return redirects;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void addRedirectUri(String uri) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
String key = SERVICEACCOUNTS_OPENSHIFT_IO_OAUTH_REDIRECTURI + "." + (i + 1);
|
||||
if (getAnnotations().containsKey(key)) continue;
|
||||
getAnnotations().put(key, uri);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JsonIgnore
|
||||
public boolean oauthWantChallenges() {
|
||||
String val = getAnnotations().get(SERVICEACCOUNTS_OPENSHIFT_IO_OAUTH_WANT_CHALLENGES);
|
||||
if (val == null) return false;
|
||||
return val.trim().equalsIgnoreCase("true");
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setOauthWantChallenges(boolean wantChallenges) {
|
||||
getAnnotations().put(SERVICEACCOUNTS_OPENSHIFT_IO_OAUTH_WANT_CHALLENGES, Boolean.toString(wantChallenges));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ServiceAccountRepresentation create(ServiceAccountRepresentation rep);
|
||||
|
||||
@GET
|
||||
@Path("{sa-name}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ServiceAccountRepresentation get(@PathParam("sa-name") String name);
|
||||
|
||||
@DELETE
|
||||
@Path("{sa-name}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response delete(@PathParam("sa-name") String name);
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.connections.rest.apis;
|
||||
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.kubernetes.KubernetesAuthentication;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.kubernetes.TokenReview;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.oauth.OpenshiftOAuth;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface Apis {
|
||||
|
||||
@Path("authentication.k8s.io/v1beta1")
|
||||
KubernetesAuthentication kubernetesAuthentication();
|
||||
|
||||
@Path("oauth.openshift.io/v1")
|
||||
OpenshiftOAuth oauth();
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.connections.rest.apis.kubernetes;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface KubernetesAuthentication {
|
||||
@Path("tokenreviews")
|
||||
TokenReview tokenReview();
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.connections.rest.apis.kubernetes;
|
||||
|
||||
import org.keycloak.protocol.openshift.TokenReviewRequestRepresentation;
|
||||
import org.keycloak.protocol.openshift.TokenReviewResponseRepresentation;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface TokenReview {
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response review(TokenReviewRequestRepresentation token);
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
String reviewPretty(TokenReviewRequestRepresentation token, @QueryParam("pretty") boolean pretty);
|
||||
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* 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.connections.rest.apis.oauth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.keycloak.protocol.openshift.connections.rest.BaseRepresentation;
|
||||
import org.keycloak.protocol.openshift.connections.rest.MetadataRepresentation;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
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.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface OAuthClients {
|
||||
|
||||
class OAuthClientRepresentation extends BaseRepresentation {
|
||||
public static OAuthClientRepresentation create() {
|
||||
OAuthClientRepresentation rep = new OAuthClientRepresentation();
|
||||
rep.wasScopeRestrictionsParsed = true;
|
||||
return rep;
|
||||
}
|
||||
|
||||
protected OAuthClientRepresentation() {
|
||||
}
|
||||
|
||||
@JsonProperty("metadata")
|
||||
protected MetadataRepresentation metadata = new MetadataRepresentation();
|
||||
|
||||
|
||||
public MetadataRepresentation getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(MetadataRepresentation metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getName() {
|
||||
return metadata.getName();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setName(String name) {
|
||||
metadata.setName(name);
|
||||
}
|
||||
|
||||
@JsonProperty("respondWithChallenges")
|
||||
protected boolean respondWithChallenges;
|
||||
|
||||
@JsonProperty("redirectURIs")
|
||||
protected Set<String> redirectURIs;
|
||||
|
||||
@JsonProperty("grantMethod")
|
||||
protected String grantMethod;
|
||||
|
||||
@JsonProperty("secret")
|
||||
protected String secret;
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public boolean isRespondWithChallenges() {
|
||||
return respondWithChallenges;
|
||||
}
|
||||
|
||||
public void setRespondWithChallenges(boolean respondWithChallenges) {
|
||||
this.respondWithChallenges = respondWithChallenges;
|
||||
}
|
||||
|
||||
public Set<String> getRedirectURIs() {
|
||||
return redirectURIs;
|
||||
}
|
||||
|
||||
public void setRedirectURIs(Set<String> redirectURIs) {
|
||||
this.redirectURIs = redirectURIs;
|
||||
}
|
||||
|
||||
public void addRedirectURI(String redirect) {
|
||||
if (this.redirectURIs == null) this.redirectURIs = new HashSet<>();
|
||||
this.redirectURIs.add(redirect);
|
||||
}
|
||||
|
||||
public String getGrantMethod() {
|
||||
return grantMethod;
|
||||
}
|
||||
|
||||
public void setGrantMethod(String grantMethod) {
|
||||
this.grantMethod = grantMethod;
|
||||
}
|
||||
|
||||
@JsonProperty("scopeRestrictions")
|
||||
protected List<Object> scopeRestrictions;
|
||||
|
||||
@JsonIgnore
|
||||
protected boolean wasScopeRestrictionsParsed;
|
||||
|
||||
@JsonIgnore
|
||||
protected Set<String> literalScopeRestrictions;
|
||||
|
||||
|
||||
@JsonIgnore
|
||||
protected List<ClusterRoleRestriction> clusterRoleRestrictions;
|
||||
|
||||
@JsonIgnore
|
||||
public Set<String> getLiteralScopeRestrictions() {
|
||||
parseScopeRestrictions();
|
||||
return literalScopeRestrictions;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<ClusterRoleRestriction> getClusterRoleRestrictions() {
|
||||
parseScopeRestrictions();
|
||||
return clusterRoleRestrictions;
|
||||
}
|
||||
|
||||
protected void parseScopeRestrictions() {
|
||||
if (wasScopeRestrictionsParsed) return;
|
||||
wasScopeRestrictionsParsed = true;
|
||||
if (scopeRestrictions == null) return;
|
||||
List<Object> marshalledScope = scopeRestrictions;
|
||||
scopeRestrictions = new LinkedList<>();
|
||||
for (Object obj : marshalledScope) {
|
||||
Map<String, Object> data = (Map<String, Object>)obj;
|
||||
if (data.containsKey("literals")) {
|
||||
if (literalScopeRestrictions == null) {
|
||||
literalScopeRestrictions = new HashSet<>();
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("literals", literalScopeRestrictions);
|
||||
scopeRestrictions.add(map);
|
||||
}
|
||||
Collection<String> scopes = (Collection<String>)data.get("literals");
|
||||
literalScopeRestrictions.addAll(scopes);
|
||||
} else if (data.containsKey("clusterRole")) {
|
||||
ClusterRoleRestriction roleRestriction = new ClusterRoleRestriction();
|
||||
Map<String, Object> roleData = (Map<String, Object>)data.get("clusterRole");
|
||||
Collection<String> namespaces = (Collection<String>)roleData.get("namespaces");
|
||||
Collection<String> roleNames = (Collection<String>)roleData.get("roleNames");
|
||||
boolean allowEscalation = roleData.containsKey("allowEscalation") ? (Boolean)roleData.get("allowEscalation") : false;
|
||||
roleRestriction.namespaces.addAll(namespaces);
|
||||
roleRestriction.roleNames.addAll(roleNames);
|
||||
roleRestriction.allowEscalation = allowEscalation;
|
||||
if (clusterRoleRestrictions == null) {
|
||||
clusterRoleRestrictions = new LinkedList<>();
|
||||
}
|
||||
clusterRoleRestrictions.add(roleRestriction);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("clusterRole", roleRestriction);
|
||||
scopeRestrictions.add(map);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addLiteralScopeRestriction(String scope) {
|
||||
if (literalScopeRestrictions == null) {
|
||||
parseScopeRestrictions();
|
||||
if (scopeRestrictions == null) {
|
||||
scopeRestrictions = new LinkedList<>();
|
||||
}
|
||||
literalScopeRestrictions = new HashSet<>();
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("literals", literalScopeRestrictions);
|
||||
scopeRestrictions.add(map);
|
||||
}
|
||||
literalScopeRestrictions.add(scope);
|
||||
}
|
||||
|
||||
public void addClusterRoleScopeRestriction(ClusterRoleRestriction restriction) {
|
||||
parseScopeRestrictions();
|
||||
if (clusterRoleRestrictions == null) {
|
||||
clusterRoleRestrictions = new LinkedList<>();
|
||||
}
|
||||
clusterRoleRestrictions.add(restriction);
|
||||
if (scopeRestrictions == null) {
|
||||
scopeRestrictions = new LinkedList<>();
|
||||
}
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("clusterRole", restriction);
|
||||
scopeRestrictions.add(map);
|
||||
}
|
||||
|
||||
private static class LiteralScopeRestriction {
|
||||
@JsonProperty("literals")
|
||||
protected Set<String> literals = new HashSet<>();
|
||||
|
||||
public Set<String> getLiterals() {
|
||||
return literals;
|
||||
}
|
||||
|
||||
public void setLiterals(Set<String> literals) {
|
||||
this.literals = literals;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClusterRoleRestriction {
|
||||
|
||||
@JsonProperty("allowEscalation")
|
||||
protected boolean allowEscalation;
|
||||
@JsonProperty("namespaces")
|
||||
protected Set<String> namespaces = new HashSet<>();
|
||||
@JsonProperty("roleNames")
|
||||
protected Set<String> roleNames = new HashSet<>();
|
||||
|
||||
public boolean isAllowEscalation() {
|
||||
return allowEscalation;
|
||||
}
|
||||
|
||||
public void setAllowEscalation(boolean allowedEscalation) {
|
||||
this.allowEscalation = allowedEscalation;
|
||||
}
|
||||
|
||||
public Set<String> getNamespaces() {
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
public void setNamespaces(Set<String> namespaces) {
|
||||
this.namespaces = namespaces;
|
||||
}
|
||||
|
||||
public Set<String> getRoleNames() {
|
||||
return roleNames;
|
||||
}
|
||||
|
||||
public void setRoleNames(Set<String> roleNames) {
|
||||
this.roleNames = roleNames;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@GET
|
||||
@Path("{name}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
OAuthClientRepresentation get(@PathParam("name") String name);
|
||||
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
Response create(OAuthClientRepresentation client);
|
||||
|
||||
@DELETE
|
||||
@Path("{name}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response delete(@PathParam("name") String name);
|
||||
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.connections.rest.apis.oauth;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface OpenshiftOAuth {
|
||||
@Path("oauthclients")
|
||||
OAuthClients clients();
|
||||
|
||||
}
|
||||
+3
@@ -39,3 +39,6 @@ org.keycloak.authentication.authenticators.x509.X509ClientCertificateAuthenticat
|
||||
org.keycloak.authentication.authenticators.x509.ValidateX509CertificateUsernameFactory
|
||||
org.keycloak.protocol.docker.DockerAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.browser.BasicAuthAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.browser.BasicAuthOTPAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.browser.NoCookieFlowRedirectAuthenticatorFactory
|
||||
+3
-1
@@ -17,4 +17,6 @@
|
||||
|
||||
org.keycloak.protocol.oidc.OIDCLoginProtocolFactory
|
||||
org.keycloak.protocol.saml.SamlProtocolFactory
|
||||
org.keycloak.protocol.docker.DockerAuthV2ProtocolFactory
|
||||
org.keycloak.protocol.docker.DockerAuthV2ProtocolFactory
|
||||
org.keycloak.protocol.openshift.OpenshiftLoginProtocolFactory
|
||||
org.keycloak.protocol.kubernetes.KubernetesLoginProtocolFactory
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.protocol.openshift.clientstorage.OpenshiftClientStorageProviderFactory
|
||||
+12
@@ -183,6 +183,18 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
|
||||
addExecInfo(execs, "OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
execs = new LinkedList<>();
|
||||
flow = newFlow(DefaultAuthenticationFlows.OPENSHIFT_CHALLENGE_FLOW, DefaultAuthenticationFlows.OPENSHIFT_CHALLENGE_FLOW_DESCRIPTION, "basic-flow", true, true);
|
||||
addExecExport(flow, null, false, "no-cookie-redirect", false, null, REQUIRED, 10);
|
||||
addExecExport(flow, null, false, "basic-auth", false, null, REQUIRED, 20);
|
||||
addExecExport(flow, null, false, "basic-auth-otp", false, null, DISABLED, 30);
|
||||
addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 40);
|
||||
addExecInfo(execs, "Browser Redirect/Refresh", "no-cookie-redirect", false, 0, 0, REQUIRED, null, new String[]{REQUIRED});
|
||||
addExecInfo(execs, "Basic Auth Challenge", "basic-auth", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Basic Auth Password+OTP", "basic-auth-otp", false, 0, 2, DISABLED, null, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 3, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
flow = newFlow("registration", "registration flow", "basic-flow", true, true);
|
||||
addExecExport(flow, "registration form", false, "registration-page-form", true, null, REQUIRED, 10);
|
||||
|
||||
|
||||
+6
@@ -146,6 +146,10 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
||||
"Validates a username and password from login form.");
|
||||
addProviderInfo(result, "auth-x509-client-username-form", "X509/Validate Username Form",
|
||||
"Validates username and password from X509 client certificate received as a part of mutual SSL handshake.");
|
||||
addProviderInfo(result, "basic-auth", "Basic Auth Challenge",
|
||||
"Basic auth challenge.");
|
||||
addProviderInfo(result, "basic-auth-otp", "Basic Auth Password+OTP",
|
||||
"Basic auth challenge with OTP. Password param should contain a combination of password + otp. Realm's OTP policy is used to determine how to parse this. This SHOULD NOT BE USED in conjection with regular basic auth provider.");
|
||||
addProviderInfo(result, "console-username-password", "Username Password Challenge",
|
||||
"Proprietary challenge protocol for CLI clients that queries for username password");
|
||||
addProviderInfo(result, "direct-grant-auth-x509-username", "X509/Validate Username",
|
||||
@@ -170,6 +174,8 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
||||
"User reviews and updates profile data retrieved from Identity Provider in the displayed form");
|
||||
addProviderInfo(result, "idp-username-password-form", "Username Password Form for identity provider reauthentication",
|
||||
"Validates a password from login form. Username is already known from identity provider authentication");
|
||||
addProviderInfo(result, "no-cookie-redirect", "Browser Redirect/Refresh",
|
||||
"Perform a 302 redirect to get user agent's current URI on authenticate path with an auth_session_id query parameter. This is for client's that do not support cookies.");
|
||||
addProviderInfo(result, "push-button-authenticator", "TEST: Button Login",
|
||||
"Just press the button to login.");
|
||||
addProviderInfo(result, "reset-credential-email", "Send Reset Email", "Send email to user and wait for response.");
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.openshift;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.protocol.openshift.clientstorage.OpenshiftClientStorageProviderFactory;
|
||||
import org.keycloak.protocol.openshift.connections.rest.OpenshiftClient;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.storage.client.ClientStorageProvider;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class AbstractOpenshiftBaseTest extends AbstractTestRealmKeycloakTest {
|
||||
/*
|
||||
$ oc create sa keycloak
|
||||
$ oc adm policy add-cluster-role-to-user system:auth-delegator -z keycloak
|
||||
$ oc adm policy add-cluster-role-to-user system:master -z keycloak
|
||||
$ oc describe sa keycloak
|
||||
# look for token and describe it
|
||||
$ oc describe secret xxxxx-xxx
|
||||
# copy token to MASTER_TOKEN constant above
|
||||
*/
|
||||
protected String addComponent(ComponentRepresentation component) {
|
||||
Response resp = adminClient.realm("test").components().add(component);
|
||||
resp.close();
|
||||
String id = ApiUtil.getCreatedId(resp);
|
||||
getCleanup().addComponentId(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void addProvidersBeforeTest() throws URISyntaxException, IOException {
|
||||
List<ComponentRepresentation> reps = adminClient.realm("test").components().query(null, ClientStorageProvider.class.getName());
|
||||
if (reps.size() > 0) return;
|
||||
ComponentRepresentation provider = new ComponentRepresentation();
|
||||
provider.setName("openshift oauth client provider");
|
||||
provider.setProviderId(OpenshiftClientStorageProviderFactory.PROVIDER_ID);
|
||||
provider.setProviderType(ClientStorageProvider.class.getName());
|
||||
provider.setConfig(new MultivaluedHashMap<>());
|
||||
provider.getConfig().putSingle(OpenshiftClientStorageProviderFactory.ACCESS_TOKEN, getMasterToken());
|
||||
provider.getConfig().putSingle(OpenshiftClientStorageProviderFactory.OPENSHIFT_URI, getOpenshiftUrl());
|
||||
|
||||
addComponent(provider);
|
||||
}
|
||||
|
||||
public static Properties config = new Properties();
|
||||
|
||||
public static final String OPENSHIFT_CONFIG = "openshift.config";
|
||||
|
||||
@BeforeClass
|
||||
public static void loadConfig() throws Exception {
|
||||
Assume.assumeTrue(System.getProperties().containsKey(OPENSHIFT_CONFIG));
|
||||
config.load(new FileInputStream(System.getProperty(OPENSHIFT_CONFIG)));
|
||||
//config.put("master_token", "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJvcGVuc2hpZnQtd2ViLWNvbnNvbGUiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoia2V5Y2xvYWstdG9rZW4tcHFmcWQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoia2V5Y2xvYWsiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI4MmU5ZTE0YS0zYWRjLTExZTgtYmZkMy0wMDBjMjk4MmM5YjgiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6b3BlbnNoaWZ0LXdlYi1jb25zb2xlOmtleWNsb2FrIn0.MP84QI0HVg6Xnk1sZhmzcXbDoN0mAdFZm061_tdyjBlqR34oKKHeZ_4q_kja7xZnmg3t0a7ZZ2mW0mafVVXwjiDucdYMkdJ5vEmi291kNz3Qut9hS8dqWBY0tt4hnwzjDr_4O6mfO66CFdjLs_9uJfqXFQpH8xi4RI98fPAqMAiqQ69tt9SVkt7ozTJI-qCSADjFlZeWnWSGP8ruX5lI9URCSlpfSiDdZeXzU6WQUh7pdjbq2mp64azfg8Nv5RfQQ5w9ZFMEhFqoJNelO5UVIN6JdpjrpLbsm5TGDIg68_RhCcdWY_lGDjE-gE2_qxulIfbKHyruKxU5uolgLaCIAw");
|
||||
//config.put("master_token", "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJteXByb2plY3QiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoia2V5Y2xvYWstdG9rZW4tY2M4cWwiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoia2V5Y2xvYWsiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIzMzg0NTU3OS0zYTg5LTExZTgtOGQ2Mi0wMDBjMjk4MmM5YjgiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6bXlwcm9qZWN0OmtleWNsb2FrIn0.KDsjSh5Mai43Q9oj9Zeokz6AphGHEV9TdhTK9y7qj-F976gUhQw7dmNOAs22m7CPjbgpZHlom6KZD0-yUP4_ZgE6r6kKw_lN_8YvM_4sMG_bZNrl9y5nk5tIrRsIexQ-inXo9AxnlMPTGClZTfePMOmoP6Ubz9OMvxZLL066kKNVRhoE0yE-Nv1o1GHnnYmx9q-LEnL5IxSmI1_fT28bceDgisxhopVAoQoFX7T5TmvBnMoZwJ6ba3ZQm7z0ISIf-ur1W77-NbYLOx9lepiT4si5OSO9qdzfUQjBc6YL8-ueU_QrmrftJYwc9tvvLOo2b0jndd6N1ApNnyx1niSdaA");
|
||||
//config.put("openshift_url", "https://192.168.238.132:8443");
|
||||
}
|
||||
|
||||
public static String getMasterToken() {
|
||||
return config.getProperty("master_token");
|
||||
}
|
||||
|
||||
public static String getOpenshiftUrl() {
|
||||
return config.getProperty("openshift_url");
|
||||
}
|
||||
|
||||
public static OpenshiftClient createOpenshiftClient() {
|
||||
return OpenshiftClient.instance(AbstractOpenshiftBaseTest.getOpenshiftUrl(), AbstractOpenshiftBaseTest.getMasterToken());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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.openshift;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.protocol.openshift.OpenshiftProtocolEndpoint;
|
||||
import org.keycloak.protocol.openshift.connections.rest.OpenshiftClient;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.Secrets;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.ServiceAccounts;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.oauth.OAuthClients;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.net.URI;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
//@Ignore
|
||||
public class CodeToTokenTest extends AbstractOpenshiftBaseTest {
|
||||
|
||||
public static final String OPENSHIFT_OAUTH_CLIENT = "literal-oauth-client";
|
||||
public static final String SERVICE_ACCOUNT = "system:serviceaccount:myproject:sa-oauth";
|
||||
public static final String SERVICE_ACCOUNT_NAME = "sa-oauth";
|
||||
public static final String OPENSHIFT_OAUTH_CLIENT_WITH_CHALLENGE = "challenge-oauth-client";
|
||||
public static final String SERVICE_ACCOUNT_WITH_CHALLENGE = "system:serviceaccount:myproject:sa-challenge";
|
||||
public static final String SERVICE_ACCOUNT_WITH_CHALLENGE_NAME = "sa-challenge";
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@Deployment
|
||||
public static WebArchive deploy() {
|
||||
return RunOnServerDeployment.create(UserResource.class)
|
||||
.addPackages(true, "org.keycloak.testsuite");
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setupTest() {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
if (session.users().getUserByUsername("reviewer", realm) != null) return;
|
||||
|
||||
ClientModel client = session.realms().getClientByClientId("test-app", realm);
|
||||
client.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
GroupModel group = realm.createGroup("openshift");
|
||||
GroupModel child = realm.createGroup("child");
|
||||
realm.moveGroup(child, group);
|
||||
|
||||
UserModel user = session.users().addUser(realm, "reviewer");
|
||||
user.setEnabled(true);
|
||||
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password"));
|
||||
user.joinGroup(child);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected String sa_token;
|
||||
protected String sa_challenge_token;
|
||||
|
||||
@Before
|
||||
public void createClients() throws Exception {
|
||||
if (sa_token != null) return;
|
||||
OpenshiftClient client = AbstractOpenshiftBaseTest.createOpenshiftClient();
|
||||
|
||||
OAuthClients.OAuthClientRepresentation rep = OAuthClients.OAuthClientRepresentation.create();
|
||||
// with literal scope restriction
|
||||
client.apis().oauth().clients().delete(OPENSHIFT_OAUTH_CLIENT).close();
|
||||
rep.setName(OPENSHIFT_OAUTH_CLIENT);
|
||||
rep.setGrantMethod("auto");
|
||||
rep.setSecret("geheim");
|
||||
rep.setRespondWithChallenges(false);
|
||||
rep.addRedirectURI("http://host1");
|
||||
rep.addRedirectURI("http://host2");
|
||||
rep.addLiteralScopeRestriction("foo");
|
||||
rep.addLiteralScopeRestriction("foo:bar");
|
||||
OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction restriction = new OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction();
|
||||
restriction.setAllowEscalation(true);
|
||||
restriction.getNamespaces().add("myproject");
|
||||
restriction.getRoleNames().add("myrole");
|
||||
rep.addClusterRoleScopeRestriction(restriction);
|
||||
restriction = new OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction();
|
||||
restriction.setAllowEscalation(false);
|
||||
restriction.getNamespaces().add("myproject");
|
||||
restriction.getRoleNames().add("another");
|
||||
rep.addClusterRoleScopeRestriction(restriction);
|
||||
|
||||
client.apis().oauth().clients().create(rep).close();
|
||||
|
||||
rep = OAuthClients.OAuthClientRepresentation.create();
|
||||
// with literal scope restriction
|
||||
client.apis().oauth().clients().delete(OPENSHIFT_OAUTH_CLIENT_WITH_CHALLENGE).close();
|
||||
rep.setName(OPENSHIFT_OAUTH_CLIENT_WITH_CHALLENGE);
|
||||
rep.setGrantMethod("auto");
|
||||
rep.setSecret("geheim");
|
||||
rep.setRespondWithChallenges(true);
|
||||
rep.addRedirectURI("http://localhost");
|
||||
client.apis().oauth().clients().create(rep).close();
|
||||
|
||||
client.api().namespace("myproject").serviceAccounts().delete(SERVICE_ACCOUNT_NAME).close();
|
||||
ServiceAccounts.ServiceAccountRepresentation sa = new ServiceAccounts.ServiceAccountRepresentation();
|
||||
sa.setName(SERVICE_ACCOUNT_NAME);
|
||||
sa.setNamespace("myproject");
|
||||
sa.setOauthWantChallenges(false);
|
||||
sa.addRedirectUri("http:/host1");
|
||||
sa = client.api().namespace("myproject").serviceAccounts().create(sa);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// sleep as generating secrets takes awhile.
|
||||
Thread.sleep(1000);
|
||||
sa = client.api().namespace("myproject").serviceAccounts().get(SERVICE_ACCOUNT_NAME);
|
||||
if (sa.getSecrets().isEmpty()) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
for (String secret : sa.getSecrets()) {
|
||||
Secrets.SecretRepresentation secretRep = client.api().namespace("myproject").secrets().get(secret);
|
||||
if (secretRep.isServiceAccountToken()) {
|
||||
sa_token = secretRep.getToken();
|
||||
//System.out.println(client.api().namespace("myproject").secrets().getPretty(secret, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.assertNotNull(sa_token);
|
||||
|
||||
client.api().namespace("myproject").serviceAccounts().delete(SERVICE_ACCOUNT_WITH_CHALLENGE_NAME).close();
|
||||
sa = new ServiceAccounts.ServiceAccountRepresentation();
|
||||
sa.setName(SERVICE_ACCOUNT_WITH_CHALLENGE_NAME);
|
||||
sa.setNamespace("myproject");
|
||||
sa.setOauthWantChallenges(true);
|
||||
sa.addRedirectUri("http://localhost");
|
||||
sa = client.api().namespace("myproject").serviceAccounts().create(sa);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// sleep as generating secrets takes awhile.
|
||||
Thread.sleep(1000);
|
||||
sa = client.api().namespace("myproject").serviceAccounts().get(SERVICE_ACCOUNT_WITH_CHALLENGE_NAME);
|
||||
if (sa.getSecrets().isEmpty()) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
for (String secret : sa.getSecrets()) {
|
||||
Secrets.SecretRepresentation secretRep = client.api().namespace("myproject").secrets().get(secret);
|
||||
if (secretRep.isServiceAccountToken()) {
|
||||
sa_challenge_token = secretRep.getToken();
|
||||
//System.out.println(client.api().namespace("myproject").secrets().getPretty(secret, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.assertNotNull(sa_challenge_token);
|
||||
|
||||
client.close();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOAuthClient() throws Exception {
|
||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||
String grantUri = getResourceOwnerPasswordCredentialGrantUrl();
|
||||
WebTarget grantTarget = httpClient.target(grantUri);
|
||||
String tokenReviewUrl = getTokenReviewUrl();
|
||||
WebTarget tokenReviewTarget = httpClient.target(tokenReviewUrl);
|
||||
oauth.clientId(OPENSHIFT_OAUTH_CLIENT_WITH_CHALLENGE);
|
||||
oauth.redirectUri("http://localhost");
|
||||
WebTarget authUrl = httpClient.target(oauth.getLoginFormUrl());
|
||||
|
||||
Response response = authUrl.request().get();
|
||||
Assert.assertEquals(302, response.getStatus());
|
||||
response.close();
|
||||
URI location = response.getLocation();
|
||||
response = httpClient.target(location).request().get();
|
||||
Assert.assertEquals(401, response.getStatus());
|
||||
Assert.assertNotNull(response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE));
|
||||
response.close();
|
||||
String header = BasicAuthHelper.createHeader("reviewer", "password");
|
||||
response = httpClient.target(location).request().header(HttpHeaders.AUTHORIZATION, header).get();
|
||||
Assert.assertEquals(302, response.getStatus());
|
||||
location = response.getLocation();
|
||||
response.close();
|
||||
Pattern codePattern = Pattern.compile("code=([^&]+)");
|
||||
String redirect = response.getLocation().toString();
|
||||
Matcher m = codePattern.matcher(redirect);
|
||||
Assert.assertTrue(m.find());
|
||||
String code = m.group(1);
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "geheim");
|
||||
Assert.assertNotNull(tokenResponse.getAccessToken());
|
||||
httpClient.close();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testServiceAccount() throws Exception {
|
||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||
String grantUri = getResourceOwnerPasswordCredentialGrantUrl();
|
||||
WebTarget grantTarget = httpClient.target(grantUri);
|
||||
String tokenReviewUrl = getTokenReviewUrl();
|
||||
WebTarget tokenReviewTarget = httpClient.target(tokenReviewUrl);
|
||||
oauth.clientId(SERVICE_ACCOUNT_WITH_CHALLENGE);
|
||||
oauth.redirectUri("http://localhost");
|
||||
WebTarget authUrl = httpClient.target(oauth.getLoginFormUrl());
|
||||
|
||||
Response response = authUrl.request().get();
|
||||
Assert.assertEquals(302, response.getStatus());
|
||||
response.close();
|
||||
URI location = response.getLocation();
|
||||
response = httpClient.target(location).request().get();
|
||||
Assert.assertEquals(401, response.getStatus());
|
||||
Assert.assertNotNull(response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE));
|
||||
response.close();
|
||||
String header = BasicAuthHelper.createHeader("reviewer", "password");
|
||||
response = httpClient.target(location).request().header(HttpHeaders.AUTHORIZATION, header).get();
|
||||
Assert.assertEquals(302, response.getStatus());
|
||||
location = response.getLocation();
|
||||
response.close();
|
||||
Pattern codePattern = Pattern.compile("code=([^&]+)");
|
||||
String redirect = response.getLocation().toString();
|
||||
Matcher m = codePattern.matcher(redirect);
|
||||
Assert.assertTrue(m.find());
|
||||
String code = m.group(1);
|
||||
|
||||
String accessTokenUrl = oauth.getAccessTokenUrl();
|
||||
Form form = new Form();
|
||||
form.param("client_id", SERVICE_ACCOUNT_WITH_CHALLENGE);
|
||||
form.param("client_secret", sa_challenge_token);
|
||||
form.param(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri());
|
||||
form.param(OAuth2Constants.CODE, code);
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE);
|
||||
response = httpClient.target(accessTokenUrl).request().post(Entity.form(form));
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
//String data = response.readEntity(String.class);
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
|
||||
//OAuthClient.AccessTokenResponse tokenResponse =response.readEntity(OAuthClient.AccessTokenResponse.class);
|
||||
Assert.assertNotNull(tokenResponse.getToken());
|
||||
httpClient.close();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getResourceOwnerPasswordCredentialGrantUrl() {
|
||||
UriBuilder b = OpenshiftProtocolEndpoint.tokenUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
|
||||
return b.build("test").toString();
|
||||
}
|
||||
|
||||
public String getTokenReviewUrl() {
|
||||
UriBuilder b = OpenshiftProtocolEndpoint.tokenReviewnUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
|
||||
return b.build("test").toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.openshift;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.openshift.OpenshiftProtocolEndpoint;
|
||||
import org.keycloak.protocol.openshift.TokenReviewRequestRepresentation;
|
||||
import org.keycloak.protocol.openshift.connections.rest.OpenshiftClient;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.Secrets;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.ServiceAccounts;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.oauth.OAuthClients;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
//@Ignore
|
||||
public class OpenshiftClientStorageTest extends AbstractOpenshiftBaseTest {
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@Deployment
|
||||
public static WebArchive deploy() {
|
||||
return RunOnServerDeployment.create(UserResource.class)
|
||||
.addPackages(true, "org.keycloak.testsuite");
|
||||
}
|
||||
|
||||
protected String sa_token;
|
||||
|
||||
@Before
|
||||
public void createClients() throws Exception {
|
||||
if (sa_token != null) return;
|
||||
OpenshiftClient client = AbstractOpenshiftBaseTest.createOpenshiftClient();
|
||||
|
||||
OAuthClients.OAuthClientRepresentation rep = OAuthClients.OAuthClientRepresentation.create();
|
||||
// with literal scope restriction
|
||||
client.apis().oauth().clients().delete("literal-oauth-client").close();
|
||||
rep.setName("literal-oauth-client");
|
||||
rep.setGrantMethod("auto");
|
||||
rep.setSecret("geheim");
|
||||
rep.setRespondWithChallenges(false);
|
||||
rep.addRedirectURI("http://host1");
|
||||
rep.addRedirectURI("http://host2");
|
||||
rep.addLiteralScopeRestriction("foo");
|
||||
rep.addLiteralScopeRestriction("foo:bar");
|
||||
client.apis().oauth().clients().create(rep).close();
|
||||
|
||||
client.api().namespace("myproject").serviceAccounts().delete("sa-oauth").close();
|
||||
ServiceAccounts.ServiceAccountRepresentation sa = new ServiceAccounts.ServiceAccountRepresentation();
|
||||
sa.setName("sa-oauth");
|
||||
sa.setNamespace("myproject");
|
||||
sa.setOauthWantChallenges(true);
|
||||
sa.addRedirectUri("http:/host1");
|
||||
sa.addRedirectUri("http:/host2");
|
||||
sa = client.api().namespace("myproject").serviceAccounts().create(sa);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// sleep as generating secrets takes awhile.
|
||||
Thread.sleep(1000);
|
||||
sa = client.api().namespace("myproject").serviceAccounts().get("sa-oauth");
|
||||
if (sa.getSecrets().isEmpty()) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
for (String secret : sa.getSecrets()) {
|
||||
Secrets.SecretRepresentation secretRep = client.api().namespace("myproject").secrets().get(secret);
|
||||
if (secretRep.isServiceAccountToken()) {
|
||||
sa_token = secretRep.getToken();
|
||||
//System.out.println(client.api().namespace("myproject").secrets().getPretty(secret, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.assertNotNull(sa_token);
|
||||
|
||||
TokenReviewRequestRepresentation request = TokenReviewRequestRepresentation.create(sa_token);
|
||||
String response = client.apis().kubernetesAuthentication().tokenReview().reviewPretty(request, true);
|
||||
//System.out.println(response);
|
||||
|
||||
|
||||
client.close();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testServer() throws Exception {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
|
||||
ClientModel client = session.realms().getClientByClientId("kcinit", realm);
|
||||
if (client != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClientModel kcinit = realm.addClient("kcinit");
|
||||
kcinit.setEnabled(true);
|
||||
kcinit.addRedirectUri("http://localhost:*");
|
||||
kcinit.setPublicClient(true);
|
||||
});
|
||||
|
||||
Thread.sleep(10000000000000l);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOAuthClient() throws Exception {
|
||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||
String grantUri = getResourceOwnerPasswordCredentialGrantUrl();
|
||||
WebTarget grantTarget = httpClient.target(grantUri);
|
||||
|
||||
String accessToken = null;
|
||||
{ // test valid password
|
||||
String header = BasicAuthHelper.createHeader("literal-oauth-client", "geheim");
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("username", "test-user@localhost");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "oauth openid");
|
||||
Response response = grantTarget.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, header)
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
}
|
||||
httpClient.close();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceAccount() throws Exception {
|
||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||
String grantUri = getResourceOwnerPasswordCredentialGrantUrl();
|
||||
WebTarget grantTarget = httpClient.target(grantUri);
|
||||
|
||||
String accessToken = null;
|
||||
{ // test valid password
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", "system:serviceaccount:myproject:sa-oauth");
|
||||
form.param("client_secret", sa_token);
|
||||
form.param("username", "test-user@localhost");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "oauth openid");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
}
|
||||
httpClient.close();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
public String getResourceOwnerPasswordCredentialGrantUrl() {
|
||||
UriBuilder b = OpenshiftProtocolEndpoint.tokenUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
|
||||
return b.build("test").toString();
|
||||
}
|
||||
|
||||
}
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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.openshift;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.protocol.openshift.TokenReviewRequestRepresentation;
|
||||
import org.keycloak.protocol.openshift.connections.rest.OpenshiftClient;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.Namespace;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.Secrets;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.ServiceAccounts;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.oauth.OAuthClients;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
//@Ignore
|
||||
public class OpenshiftClientTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void loadConfig() throws Exception {
|
||||
Assume.assumeTrue(System.getProperties().containsKey(AbstractOpenshiftBaseTest.OPENSHIFT_CONFIG));
|
||||
AbstractOpenshiftBaseTest.config.load(new FileInputStream(System.getProperty(AbstractOpenshiftBaseTest.OPENSHIFT_CONFIG)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testServiceAccount() throws Exception {
|
||||
|
||||
OpenshiftClient client = AbstractOpenshiftBaseTest.createOpenshiftClient();
|
||||
Namespace myproject = client.api().namespace("myproject");
|
||||
|
||||
// test not found
|
||||
try {
|
||||
myproject.serviceAccounts().get("notfound");
|
||||
Assert.fail();
|
||||
} catch (NotFoundException e) {
|
||||
if (e.getResponse() != null) e.getResponse().close();
|
||||
}
|
||||
|
||||
Response response = myproject.serviceAccounts().delete("sa-oauth");
|
||||
response.close();
|
||||
ServiceAccounts.ServiceAccountRepresentation rep = new ServiceAccounts.ServiceAccountRepresentation();
|
||||
rep.setName("sa-oauth");
|
||||
rep.setNamespace("myproject");
|
||||
rep.setOauthWantChallenges(true);
|
||||
rep.addRedirectUri("http:/host1");
|
||||
rep.addRedirectUri("http:/host2");
|
||||
rep = myproject.serviceAccounts().create(rep);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// sleep as generating secrets takes awhile.
|
||||
Thread.sleep(1000);
|
||||
rep = myproject.serviceAccounts().get("sa-oauth");
|
||||
if (rep.getSecrets().isEmpty()) continue;
|
||||
break;
|
||||
}
|
||||
Assert.assertEquals("sa-oauth", rep.getName());
|
||||
Assert.assertEquals("myproject", rep.getNamespace());
|
||||
Assert.assertTrue(rep.getOauthRedirectUris().contains("http:/host1"));
|
||||
Assert.assertTrue(rep.getOauthRedirectUris().contains("http:/host2"));
|
||||
Assert.assertTrue(rep.oauthWantChallenges());
|
||||
|
||||
String token = null;
|
||||
for (String secret : rep.getSecrets()) {
|
||||
Secrets.SecretRepresentation secretRep = myproject.secrets().get(secret);
|
||||
if (secretRep.isServiceAccountToken()) {
|
||||
token = secretRep.getToken();
|
||||
}
|
||||
}
|
||||
Assert.assertNotNull(token);
|
||||
|
||||
|
||||
response = myproject.serviceAccounts().delete("sa-oauth");
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
response.close();
|
||||
try {
|
||||
rep = myproject.serviceAccounts().get("sa-oauth");
|
||||
Assert.fail("should be deleted already");
|
||||
} catch (NotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenReview() throws Exception {
|
||||
OpenshiftClient client = AbstractOpenshiftBaseTest.createOpenshiftClient();
|
||||
TokenReviewRequestRepresentation request = TokenReviewRequestRepresentation.create(AbstractOpenshiftBaseTest.getMasterToken());
|
||||
|
||||
//TokenReviewResponseRepresentation review = client.apis().kubernetesAuthentication("v1beta1").tokenReview().review(request);
|
||||
Response response = client.apis().kubernetesAuthentication().tokenReview().review(request);
|
||||
String data = response.readEntity(String.class);
|
||||
//System.out.println(data);
|
||||
|
||||
|
||||
}
|
||||
@Test
|
||||
public void testOAuthClients() throws Exception {
|
||||
OpenshiftClient client = AbstractOpenshiftBaseTest.createOpenshiftClient();
|
||||
|
||||
OAuthClients.OAuthClientRepresentation rep = OAuthClients.OAuthClientRepresentation.create();
|
||||
// with literal scope restriction
|
||||
client.apis().oauth().clients().delete("literal-oauth-client").close();
|
||||
rep.setName("literal-oauth-client");
|
||||
rep.setGrantMethod("auto");
|
||||
rep.setSecret("geheim");
|
||||
rep.setRespondWithChallenges(false);
|
||||
rep.addRedirectURI("http://host1");
|
||||
rep.addRedirectURI("http://host2");
|
||||
rep.addLiteralScopeRestriction("foo");
|
||||
rep.addLiteralScopeRestriction("foo:bar");
|
||||
client.apis().oauth().clients().create(rep).close();
|
||||
rep = client.apis().oauth().clients().get("literal-oauth-client");
|
||||
|
||||
Assert.assertEquals("literal-oauth-client", rep.getName());
|
||||
Assert.assertEquals("auto", rep.getGrantMethod());
|
||||
Assert.assertEquals("geheim", rep.getSecret());
|
||||
Assert.assertFalse(rep.isRespondWithChallenges());
|
||||
Assert.assertTrue(rep.getRedirectURIs().contains("http://host1"));
|
||||
Assert.assertTrue(rep.getRedirectURIs().contains("http://host2"));
|
||||
Assert.assertTrue(rep.getLiteralScopeRestrictions().contains("foo"));
|
||||
Assert.assertTrue(rep.getLiteralScopeRestrictions().contains("foo:bar"));
|
||||
|
||||
client.apis().oauth().clients().delete("literal-oauth-client").close();
|
||||
|
||||
// with cluster role scope restriction
|
||||
|
||||
client.apis().oauth().clients().delete("role-oauth-client").close();
|
||||
|
||||
rep = OAuthClients.OAuthClientRepresentation.create();
|
||||
rep.setName("role-oauth-client");
|
||||
rep.setGrantMethod("prompt");
|
||||
rep.setSecret("geheim");
|
||||
rep.setRespondWithChallenges(true);
|
||||
rep.addRedirectURI("http://host1");
|
||||
OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction restriction = new OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction();
|
||||
restriction.setAllowEscalation(true);
|
||||
restriction.getNamespaces().add("foo");
|
||||
restriction.getRoleNames().add("bar");
|
||||
rep.addClusterRoleScopeRestriction(restriction);
|
||||
client.apis().oauth().clients().create(rep).close();
|
||||
rep = client.apis().oauth().clients().get("role-oauth-client");
|
||||
|
||||
Assert.assertEquals("role-oauth-client", rep.getName());
|
||||
Assert.assertEquals("prompt", rep.getGrantMethod());
|
||||
Assert.assertEquals("geheim", rep.getSecret());
|
||||
Assert.assertTrue(rep.isRespondWithChallenges());
|
||||
Assert.assertTrue(rep.getRedirectURIs().contains("http://host1"));
|
||||
Assert.assertEquals(1, rep.getClusterRoleRestrictions().size());
|
||||
OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction testRestriction = rep.getClusterRoleRestrictions().get(0);
|
||||
Assert.assertTrue(testRestriction.isAllowEscalation());
|
||||
Assert.assertTrue(testRestriction.getNamespaces().contains("foo"));
|
||||
Assert.assertTrue(testRestriction.getRoleNames().contains("bar"));
|
||||
|
||||
client.apis().oauth().clients().delete("role-oauth-client").close();
|
||||
|
||||
|
||||
// with both literal and cluster role scope restriction
|
||||
|
||||
client.apis().oauth().clients().delete("both-oauth-client").close();
|
||||
|
||||
rep = OAuthClients.OAuthClientRepresentation.create();
|
||||
rep.setName("both-oauth-client");
|
||||
rep.setGrantMethod("prompt");
|
||||
rep.setSecret("geheim");
|
||||
rep.setRespondWithChallenges(true);
|
||||
rep.addRedirectURI("http://host1");
|
||||
rep.addClusterRoleScopeRestriction(restriction);
|
||||
rep.addLiteralScopeRestriction("foo");
|
||||
rep.addLiteralScopeRestriction("foo:bar");
|
||||
client.apis().oauth().clients().create(rep).close();
|
||||
rep = client.apis().oauth().clients().get("both-oauth-client");
|
||||
|
||||
Assert.assertEquals("both-oauth-client", rep.getName());
|
||||
Assert.assertEquals("prompt", rep.getGrantMethod());
|
||||
Assert.assertEquals("geheim", rep.getSecret());
|
||||
Assert.assertTrue(rep.isRespondWithChallenges());
|
||||
Assert.assertTrue(rep.getRedirectURIs().contains("http://host1"));
|
||||
Assert.assertEquals(1, rep.getClusterRoleRestrictions().size());
|
||||
testRestriction = rep.getClusterRoleRestrictions().get(0);
|
||||
Assert.assertTrue(testRestriction.isAllowEscalation());
|
||||
Assert.assertTrue(testRestriction.getNamespaces().contains("foo"));
|
||||
Assert.assertTrue(testRestriction.getRoleNames().contains("bar"));
|
||||
Assert.assertTrue(rep.getLiteralScopeRestrictions().contains("foo"));
|
||||
Assert.assertTrue(rep.getLiteralScopeRestrictions().contains("foo:bar"));
|
||||
|
||||
client.apis().oauth().clients().delete("both-oauth-client").close();
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
* 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.openshift;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.protocol.openshift.OpenshiftProtocolEndpoint;
|
||||
import org.keycloak.protocol.openshift.TokenReviewRequestRepresentation;
|
||||
import org.keycloak.protocol.openshift.TokenReviewResponseRepresentation;
|
||||
import org.keycloak.protocol.openshift.connections.rest.OpenshiftClient;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.Secrets;
|
||||
import org.keycloak.protocol.openshift.connections.rest.api.v1.ServiceAccounts;
|
||||
import org.keycloak.protocol.openshift.connections.rest.apis.oauth.OAuthClients;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
//@Ignore
|
||||
public class TokenReviewEndpointTest extends AbstractOpenshiftBaseTest {
|
||||
|
||||
public static final String OPENSHIFT_OAUTH_CLIENT = "literal-oauth-client";
|
||||
public static final String SERVICE_ACCOUNT = "system:serviceaccount:myproject:sa-oauth";
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@Deployment
|
||||
public static WebArchive deploy() {
|
||||
return RunOnServerDeployment.create(UserResource.class)
|
||||
.addPackages(true, "org.keycloak.testsuite");
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setupTest() {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
if (session.users().getUserByUsername("reviewer", realm) != null) return;
|
||||
|
||||
ClientModel client = session.realms().getClientByClientId("test-app", realm);
|
||||
client.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
GroupModel group = realm.createGroup("openshift");
|
||||
GroupModel child = realm.createGroup("child");
|
||||
realm.moveGroup(child, group);
|
||||
|
||||
UserModel user = session.users().addUser(realm, "reviewer");
|
||||
user.setEnabled(true);
|
||||
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password"));
|
||||
user.joinGroup(child);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected String sa_token;
|
||||
|
||||
@Before
|
||||
public void createClients() throws Exception {
|
||||
if (sa_token != null) return;
|
||||
OpenshiftClient client = createOpenshiftClient();
|
||||
|
||||
OAuthClients.OAuthClientRepresentation rep = OAuthClients.OAuthClientRepresentation.create();
|
||||
// with literal scope restriction
|
||||
client.apis().oauth().clients().delete(OPENSHIFT_OAUTH_CLIENT).close();
|
||||
rep.setName(OPENSHIFT_OAUTH_CLIENT);
|
||||
rep.setGrantMethod("auto");
|
||||
rep.setSecret("geheim");
|
||||
rep.setRespondWithChallenges(false);
|
||||
rep.addRedirectURI("http://host1");
|
||||
rep.addRedirectURI("http://host2");
|
||||
rep.addLiteralScopeRestriction("foo");
|
||||
rep.addLiteralScopeRestriction("foo:bar");
|
||||
OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction restriction = new OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction();
|
||||
restriction.setAllowEscalation(true);
|
||||
restriction.getNamespaces().add("myproject");
|
||||
restriction.getRoleNames().add("myrole");
|
||||
rep.addClusterRoleScopeRestriction(restriction);
|
||||
restriction = new OAuthClients.OAuthClientRepresentation.ClusterRoleRestriction();
|
||||
restriction.setAllowEscalation(false);
|
||||
restriction.getNamespaces().add("myproject");
|
||||
restriction.getRoleNames().add("another");
|
||||
rep.addClusterRoleScopeRestriction(restriction);
|
||||
|
||||
client.apis().oauth().clients().create(rep).close();
|
||||
|
||||
client.api().namespace("myproject").serviceAccounts().delete("sa-oauth").close();
|
||||
ServiceAccounts.ServiceAccountRepresentation sa = new ServiceAccounts.ServiceAccountRepresentation();
|
||||
sa.setName("sa-oauth");
|
||||
sa.setNamespace("myproject");
|
||||
sa.setOauthWantChallenges(true);
|
||||
sa.addRedirectUri("http:/host1");
|
||||
sa.addRedirectUri("http:/host2");
|
||||
sa = client.api().namespace("myproject").serviceAccounts().create(sa);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// sleep as generating secrets takes awhile.
|
||||
Thread.sleep(1000);
|
||||
sa = client.api().namespace("myproject").serviceAccounts().get("sa-oauth");
|
||||
if (sa.getSecrets().isEmpty()) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
for (String secret : sa.getSecrets()) {
|
||||
Secrets.SecretRepresentation secretRep = client.api().namespace("myproject").secrets().get(secret);
|
||||
if (secretRep.isServiceAccountToken()) {
|
||||
sa_token = secretRep.getToken();
|
||||
//System.out.println(client.api().namespace("myproject").secrets().getPretty(secret, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.assertNotNull(sa_token);
|
||||
|
||||
client.close();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOAuthClient() throws Exception {
|
||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||
String grantUri = getResourceOwnerPasswordCredentialGrantUrl();
|
||||
WebTarget grantTarget = httpClient.target(grantUri);
|
||||
String tokenReviewUrl = getTokenReviewUrl();
|
||||
WebTarget tokenReviewTarget = httpClient.target(tokenReviewUrl);
|
||||
|
||||
|
||||
// test good scope/token
|
||||
{
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", OPENSHIFT_OAUTH_CLIENT);
|
||||
form.param("client_secret", "geheim");
|
||||
form.param("username", "reviewer");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "foo foo:bar role:myrole:myproject");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
String accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
|
||||
response = tokenReviewTarget.request()
|
||||
.post(Entity.json(TokenReviewRequestRepresentation.build(accessToken)));
|
||||
assertEquals(200, response.getStatus());
|
||||
String reviewString = response.readEntity(String.class);
|
||||
//System.out.println(reviewString);
|
||||
TokenReviewResponseRepresentation review = JsonSerialization.readValue(reviewString, TokenReviewResponseRepresentation.class);
|
||||
Assert.assertTrue(review.getStatus().isAuthenticated());
|
||||
TokenReviewResponseRepresentation.Status.User user = review.getStatus().getUser();
|
||||
Assert.assertEquals("reviewer", user.getUsername());
|
||||
Assert.assertTrue(user.getGroups().contains("openshift"));
|
||||
Assert.assertTrue(user.getGroups().contains("openshift:child"));
|
||||
List<String> scopes = (List<String>)user.getExtra().getData().get("scopes.authorization.openshift.io");
|
||||
Assert.assertTrue(scopes.contains("foo"));
|
||||
Assert.assertTrue(scopes.contains("foo:bar"));
|
||||
Assert.assertTrue(scopes.contains("role:myrole:myproject"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
// test escalation
|
||||
{
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", OPENSHIFT_OAUTH_CLIENT);
|
||||
form.param("client_secret", "geheim");
|
||||
form.param("username", "reviewer");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "role:myrole:myproject:! role:another:myproject");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
String accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
|
||||
response = tokenReviewTarget.request()
|
||||
.post(Entity.json(TokenReviewRequestRepresentation.build(accessToken)));
|
||||
assertEquals(200, response.getStatus());
|
||||
String reviewString = response.readEntity(String.class);
|
||||
//System.out.println(reviewString);
|
||||
TokenReviewResponseRepresentation review = JsonSerialization.readValue(reviewString, TokenReviewResponseRepresentation.class);
|
||||
Assert.assertTrue(review.getStatus().isAuthenticated());
|
||||
TokenReviewResponseRepresentation.Status.User user = review.getStatus().getUser();
|
||||
Assert.assertEquals("reviewer", user.getUsername());
|
||||
List<String> scopes = (List<String>)user.getExtra().getData().get("scopes.authorization.openshift.io");
|
||||
Assert.assertTrue(scopes.contains("role:myrole:myproject:!"));
|
||||
Assert.assertTrue(scopes.contains("role:another:myproject"));
|
||||
|
||||
}
|
||||
// test bad scope
|
||||
{
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", OPENSHIFT_OAUTH_CLIENT);
|
||||
form.param("client_secret", "geheim");
|
||||
form.param("username", "reviewer");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "bad_scope");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
String accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
|
||||
response = tokenReviewTarget.request()
|
||||
.post(Entity.json(TokenReviewRequestRepresentation.build(accessToken)));
|
||||
assertEquals(401, response.getStatus());
|
||||
String reviewString = response.readEntity(String.class);
|
||||
//System.out.println(reviewString);
|
||||
TokenReviewResponseRepresentation review = JsonSerialization.readValue(reviewString, TokenReviewResponseRepresentation.class);
|
||||
Assert.assertFalse(review.getStatus().isAuthenticated());
|
||||
Assert.assertEquals(Errors.INVALID_SCOPE, review.getStatus().getError());
|
||||
}
|
||||
|
||||
// test bad escalation
|
||||
{
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", OPENSHIFT_OAUTH_CLIENT);
|
||||
form.param("client_secret", "geheim");
|
||||
form.param("username", "reviewer");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "role:another:myproject:!");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
String accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
|
||||
response = tokenReviewTarget.request()
|
||||
.post(Entity.json(TokenReviewRequestRepresentation.build(accessToken)));
|
||||
assertEquals(401, response.getStatus());
|
||||
String reviewString = response.readEntity(String.class);
|
||||
//System.out.println(reviewString);
|
||||
TokenReviewResponseRepresentation review = JsonSerialization.readValue(reviewString, TokenReviewResponseRepresentation.class);
|
||||
Assert.assertFalse(review.getStatus().isAuthenticated());
|
||||
Assert.assertEquals(Errors.INVALID_SCOPE, review.getStatus().getError());
|
||||
}
|
||||
|
||||
|
||||
httpClient.close();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceAccount() throws Exception {
|
||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||
String grantUri = getResourceOwnerPasswordCredentialGrantUrl();
|
||||
WebTarget grantTarget = httpClient.target(grantUri);
|
||||
String tokenReviewUrl = getTokenReviewUrl();
|
||||
WebTarget tokenReviewTarget = httpClient.target(tokenReviewUrl);
|
||||
|
||||
|
||||
// test good scope/token
|
||||
{
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", SERVICE_ACCOUNT);
|
||||
form.param("client_secret", sa_token);
|
||||
form.param("username", "reviewer");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "user:info user:check-access role:myrole:myproject role:another:myproject:!");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
String accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
|
||||
response = tokenReviewTarget.request()
|
||||
.post(Entity.json(TokenReviewRequestRepresentation.build(accessToken)));
|
||||
assertEquals(200, response.getStatus());
|
||||
String reviewString = response.readEntity(String.class);
|
||||
//System.out.println(reviewString);
|
||||
TokenReviewResponseRepresentation review = JsonSerialization.readValue(reviewString, TokenReviewResponseRepresentation.class);
|
||||
Assert.assertTrue(review.getStatus().isAuthenticated());
|
||||
TokenReviewResponseRepresentation.Status.User user = review.getStatus().getUser();
|
||||
Assert.assertEquals("reviewer", user.getUsername());
|
||||
Assert.assertTrue(user.getGroups().contains("openshift"));
|
||||
Assert.assertTrue(user.getGroups().contains("openshift:child"));
|
||||
List<String> scopes = (List<String>)user.getExtra().getData().get("scopes.authorization.openshift.io");
|
||||
Assert.assertTrue(scopes.contains("user:info"));
|
||||
Assert.assertTrue(scopes.contains("user:check-access"));
|
||||
Assert.assertTrue(scopes.contains("role:myrole:myproject"));
|
||||
Assert.assertTrue(scopes.contains("role:another:myproject:!"));
|
||||
|
||||
}
|
||||
|
||||
// test bad scope
|
||||
{
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", SERVICE_ACCOUNT);
|
||||
form.param("client_secret", sa_token);
|
||||
form.param("username", "reviewer");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "bad_scope");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
String accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
|
||||
response = tokenReviewTarget.request()
|
||||
.post(Entity.json(TokenReviewRequestRepresentation.build(accessToken)));
|
||||
assertEquals(401, response.getStatus());
|
||||
String reviewString = response.readEntity(String.class);
|
||||
//System.out.println(reviewString);
|
||||
TokenReviewResponseRepresentation review = JsonSerialization.readValue(reviewString, TokenReviewResponseRepresentation.class);
|
||||
Assert.assertFalse(review.getStatus().isAuthenticated());
|
||||
Assert.assertEquals(Errors.INVALID_SCOPE, review.getStatus().getError());
|
||||
}
|
||||
|
||||
// test bad role namespace
|
||||
{
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", SERVICE_ACCOUNT);
|
||||
form.param("client_secret", sa_token);
|
||||
form.param("username", "reviewer");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "role:another:badproject");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
String accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
|
||||
response = tokenReviewTarget.request()
|
||||
.post(Entity.json(TokenReviewRequestRepresentation.build(accessToken)));
|
||||
assertEquals(401, response.getStatus());
|
||||
String reviewString = response.readEntity(String.class);
|
||||
//System.out.println(reviewString);
|
||||
TokenReviewResponseRepresentation review = JsonSerialization.readValue(reviewString, TokenReviewResponseRepresentation.class);
|
||||
Assert.assertFalse(review.getStatus().isAuthenticated());
|
||||
Assert.assertEquals(Errors.INVALID_SCOPE, review.getStatus().getError());
|
||||
}
|
||||
|
||||
// test bad role namespace
|
||||
{
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("client_id", SERVICE_ACCOUNT);
|
||||
form.param("client_secret", sa_token);
|
||||
form.param("username", "reviewer");
|
||||
form.param("password", "password");
|
||||
form.param("scope", "role:another:badproject:!");
|
||||
Response response = grantTarget.request()
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class);
|
||||
String accessToken = tokenResponse.getToken();
|
||||
Assert.assertNotNull(accessToken);
|
||||
response.close();
|
||||
|
||||
response = tokenReviewTarget.request()
|
||||
.post(Entity.json(TokenReviewRequestRepresentation.build(accessToken)));
|
||||
assertEquals(401, response.getStatus());
|
||||
String reviewString = response.readEntity(String.class);
|
||||
//System.out.println(reviewString);
|
||||
TokenReviewResponseRepresentation review = JsonSerialization.readValue(reviewString, TokenReviewResponseRepresentation.class);
|
||||
Assert.assertFalse(review.getStatus().isAuthenticated());
|
||||
Assert.assertEquals(Errors.INVALID_SCOPE, review.getStatus().getError());
|
||||
}
|
||||
|
||||
|
||||
httpClient.close();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
public String getResourceOwnerPasswordCredentialGrantUrl() {
|
||||
UriBuilder b = OpenshiftProtocolEndpoint.tokenUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
|
||||
return b.build("test").toString();
|
||||
}
|
||||
|
||||
public String getTokenReviewUrl() {
|
||||
UriBuilder b = OpenshiftProtocolEndpoint.tokenReviewnUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
|
||||
return b.build("test").toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user