8000 Credentials Refactoring by patriot1burke · Pull Request #3258 · keycloak/keycloak · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Credentials Refactoring #3258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

package org.keycloak.representations.idm;

import org.keycloak.common.util.MultivaluedHashMap;

import java.util.Map;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
Expand Down Expand Up @@ -45,6 +49,7 @@ public class CredentialRepresentation {
private Integer digits;
private Integer period;
private Long createdDate;
private MultivaluedHashMap<String, String> config;

// only used when updating a credential. Might set required action
protected Boolean temporary;
Expand Down Expand Up @@ -144,4 +149,12 @@ public Long getCreatedDate() {
public void setCreatedDate(Long createdDate) {
this.createdDate = createdDate;
}

public MultivaluedHashMap<String, String> getConfig() {
return config;
}

public void setConfig(MultivaluedHashMap<String, String> config) {
this.config = config;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,12 @@ public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}

@Deprecated
public Boolean isTotp() {
return totp;
}

@Deprecated
public void setTotp(Boolean totp) {
this.totp = totp;
}
Expand Down
2 changes: 1 addition & 1 deletion distribution/demo-dist/src/main/xslt/standalone.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<xsl:copy>
<xsl:apply-templates select="node()[name(.)='datasource']"/>
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" use-java-context="true">
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
<xa-datasource-property name="URL">jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</xa-datasource-property>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
Expand Down
32 changes: 12 additions & 20 deletions examples/providers/authenticator/README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
Example Custom Authenticator
===================================================

This is an example of defining a custom Authenticator and Required action. This example is explained in the user documentation
of Keycloak. To deploy, build this directory then take the jar and copy it to providers directory. Alternatively you can deploy as a module by running:
1. First, Keycloak must be running.

KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.secret-question --resources=target/authenticator-required-action-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-services,org.jboss.resteasy.resteasy-jaxrs,javax.ws.rs.api"
2. Execute the follow. This will build the example and deploy it

Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
$ mvn clean install wildfly:deploy

<providers>
...
<provider>module:org.keycloak.examples.secret-question</provider>
</providers>
3. Copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.

You then have to copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
4. Login to admin console. Hit browser refresh if you are already logged in so that the new providers show up.

After you do all this, you then have to reboot keycloak. When reboot is complete, you will need to log into
the admin console to create a new flow with your new authenticator.
5. Go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
defined flows. You cannot modify an built in flows, so, to add the Authenticator you
have to copy an existing flow or create your own. Copy the "Browser" flow.

If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
defined flows. You cannot modify an built in flows, so, to add the Authenticator you
have to copy an existing flow or create your own.
6. In your copy, click the "Actions" menu item and "Add Execution". Pick Secret Question

Next you have to register your required action.
Click on the Required Actions tab. Click on the Register button and choose your new Required Action.
Your new required action should now be displayed and enabled in the required actions list.
7. Next you have to register the required action that you created. Click on the Required Actions tab in the Authenticaiton 10000 menu.
Click on the Register button and choose your new Required Action.
Your new required action should now be displayed and enabled in the required actions list.

I'm hoping the UI is intuitive enough so that you
can figure out for yourself how to create a flow and add the Authenticator and Required Action. We're looking to add a screencast
to show this in action.
17 changes: 17 additions & 0 deletions examples/providers/authenticator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,22 @@

<build>
<finalName>authenticator-required-action-example</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@

package org.keycloak.examples.authenticator;

import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
import org.keycloak.common.util.ServerCookie;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.util.CookieHelper;

import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.net.URI;
Expand Down 17AE Expand Up @@ -86,25 +89,29 @@ protected void setCookie(AuthenticationFlowContext context) {

}
URI uri = context.getUriInfo().getBaseUriBuilder().path("realms").path(context.getRealm().getName()).build();
CookieHelper.addCookie("SECRET_QUESTION_ANSWERED", "true",
addCookie("SECRET_QUESTION_ANSWERED", "true",
uri.getRawPath(),
null, null,
maxCookieAge,
false, true);
}

public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
StringBuffer cookieBuf = new StringBuffer();
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly);
String cookie = cookieBuf.toString();
response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
}


protected boolean validateAnswer(AuthenticationFlowContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String secret = formData.getFirst("secret_answer");
UserCredentialValueModel cred = null;
for (UserCredentialValueModel model : context.getUser().getCredentialsDirectly()) {
if (model.getType().equals(CREDENTIAL_TYPE)) {
cred = model;
break;
}
}

return cred.getValue().equals(secret);
UserCredentialModel input = new UserCredentialModel();
input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
input.setValue(secret);
return context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(), input);
}

@Override
Expand All @@ -114,7 +121,7 @@ public boolean requiresUser() {

@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return session.users().configuredForCredentialType(CREDENTIAL_TYPE, realm, user);
return session.userCredentialManager().isConfiguredFor(realm, user, SecretQuestionCredentialProvider.SECRET_QUESTION);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -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.examples.authenticator;

import org.keycloak.common.util.Time;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.CredentialProvider;
import org.keycloak.credential.PasswordCredentialProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.CachedUserModel;
import org.keycloak.models.cache.OnUserCache;

import java.util.List;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SecretQuestionCredentialProvider implements CredentialProvider, CredentialInputValidator, CredentialInputUpdater, OnUserCache {
public static final String SECRET_QUESTION = "SECRET_QUESTION";
public static final String CACHE_KEY = SecretQuestionCredentialProvider.class.getName() + "." + SECRET_QUESTION;

protected KeycloakSession session;

public SecretQuestionCredentialProvider(KeycloakSession session) {
this.session = session;
}

public CredentialModel getSecret(RealmModel realm, UserModel user) {
CredentialModel secret = null;
if (user instanceof CachedUserModel) {
CachedUserModel cached = (CachedUserModel)user;
secret = (CredentialModel)cached.getCachedWith().get(CACHE_KEY);

} else {
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
if (!creds.isEmpty()) secret = creds.get(0);
}
return secret;
}


@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!SECRET_QUESTION.equals(input.getType())) return false;
if (!(input instanceof UserCredentialModel)) return false;
UserCredentialModel credInput = (UserCredentialModel) input;
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
if (creds.isEmpty()) {
CredentialModel secret = new CredentialModel();
secret.setType(SECRET_QUESTION);
secret.setValue(credInput.getValue());
secret.setCreatedDate(Time.currentTimeMillis());
session.userCredentialManager().createCredential(realm ,user, secret);
} else {
creds.get(0).setValue(credInput.getValue());
session.userCredentialManager().updateCredential(realm, user, creds.get(0));
}
session.getUserCache().evict(realm, user);
return true;
}

@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
if (!SECRET_QUESTION.equals(credentialType)) return;
session.userCredentialManager().disableCredential(realm, user, credentialType);
session.getUserCache().evict(realm, user);

}

@Override
public boolean supportsCredentialType(String credentialType) {
return SECRET_QUESTION.equals(credentialType);
}

@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
if (!SECRET_QUESTION.equals(credentialType)) return false;
return getSecret(realm, user) != null;
}

@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (!SECRET_QUESTION.equals(input.getType())) return false;
if (!(input instanceof UserCredentialModel)) return false;

String secret = getSecret(realm, user).getValue();

return secret != null && ((UserCredentialModel)input).getValue().equals(secret);
}

@Override
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
if (!creds.isEmpty()) user.getCachedWith().put(CACHE_KEY, creds.get(0));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.examples.authenticator;

import org.keycloak.credential.CredentialProvider;
import org.keycloak.credential.CredentialProviderFactory;
import org.keycloak.models.KeycloakSession;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SecretQuestionCredentialProviderFactory implements CredentialProviderFactory<SecretQuestionCredentialProvider> {
@Override
public String getId() {
return "secret-question";
}

@Override
public CredentialProvider create(KeycloakSession session) {
return new SecretQuestionCredentialProvider(session);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;

import javax.ws.rs.core.Response;
Expand All @@ -45,10 +46,10 @@ public void requiredActionChallenge(RequiredActionContext context) {
@Override
public void processAction(RequiredActionContext context) {
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("secret_answer"));
UserCredentialValueModel model = new UserCredentialValueModel();
model.setValue(answer);
model.setType(SecretQuestionAuthenticator.CREDENTIAL_TYPE);
context.getUser().updateCredentialDirectly(model);
UserCredentialModel input = new UserCredentialModel();
input.setType(SecretQuestionCredentialProvider.SECRET_QUESTION);
input.setValue(answer);
context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), input);
context.success();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.keycloak.examples.authenticator.SecretQuestionCredentialProviderFactory
Loading
0