Merge pull request #404 from stianst/master

Add realm option to enable/disable Resource Owner Password Credentials G...
This commit is contained in:
Stian Thorgersen 2014-05-20 11:33:07 +01:00
commit 3a15b416a9
12 changed files with 109 additions and 19 deletions

View file

@ -20,6 +20,7 @@ public class RealmRepresentation {
protected Integer accessCodeLifespanUserAction; protected Integer accessCodeLifespanUserAction;
protected Boolean enabled; protected Boolean enabled;
protected Boolean sslNotRequired; protected Boolean sslNotRequired;
protected Boolean passwordCredentialGrantAllowed;
protected Boolean registrationAllowed; protected Boolean registrationAllowed;
protected Boolean rememberMe; protected Boolean rememberMe;
protected Boolean verifyEmail; protected Boolean verifyEmail;
@ -242,6 +243,14 @@ public class RealmRepresentation {
this.publicKey = publicKey; this.publicKey = publicKey;
} }
public Boolean isPasswordCredentialGrantAllowed() {
return passwordCredentialGrantAllowed;
}
public void setPasswordCredentialGrantAllowed(Boolean passwordCredentialGrantAllowed) {
this.passwordCredentialGrantAllowed = passwordCredentialGrantAllowed;
}
public Boolean isRegistrationAllowed() { public Boolean isRegistrationAllowed() {
return registrationAllowed; return registrationAllowed;
} }

View file

@ -66,6 +66,12 @@
<input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch /> <input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch />
</div> </div>
</div> </div>
<div class="form-group">
<label for="passwordCredentialGrantAllowed" class="col-sm-2 control-label">Password Credential Grant</label>
<div class="col-sm-4">
<input ng-model="realm.passwordCredentialGrantAllowedpasswordCredentialGrantAllowed" name="passwordCredentialGrantAllowed" id="passwordCredentialGrantAllowed" onoffswitch />
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="requireSsl" class="col-sm-2 control-label">Require SSL</label> <label for="requireSsl" class="col-sm-2 control-label">Require SSL</label>
<div class="col-sm-4"> <div class="col-sm-4">

View file

@ -29,6 +29,11 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
boolean isRegistrationAllowed(); boolean isRegistrationAllowed();
void setRegistrationAllowed(boolean registrationAllowed); void setRegistrationAllowed(boolean registrationAllowed);
boolean isPasswordCredentialGrantAllowed();
void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed);
boolean isRememberMe(); boolean isRememberMe();
void setRememberMe(boolean rememberMe); void setRememberMe(boolean rememberMe);

View file

@ -16,6 +16,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private boolean registrationAllowed; private boolean registrationAllowed;
private boolean rememberMe; private boolean rememberMe;
private boolean verifyEmail; private boolean verifyEmail;
private boolean passwordCredentialGrantAllowed;
private boolean resetPasswordAllowed; private boolean resetPasswordAllowed;
private boolean social; private boolean social;
private boolean updateProfileOnInitialSocialLogin; private boolean updateProfileOnInitialSocialLogin;
@ -85,6 +86,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.sslNotRequired = sslNotRequired; this.sslNotRequired = sslNotRequired;
} }
public boolean isPasswordCredentialGrantAllowed() {
return passwordCredentialGrantAllowed;
}
public void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed) {
this.passwordCredentialGrantAllowed = passwordCredentialGrantAllowed;
}
public boolean isRegistrationAllowed() { public boolean isRegistrationAllowed() {
return registrationAllowed; return registrationAllowed;
} }

View file

@ -114,6 +114,17 @@ public class RealmAdapter implements RealmModel {
em.flush(); em.flush();
} }
@Override
public boolean isPasswordCredentialGrantAllowed() {
return realm.isPasswordCredentialGrantAllowed();
}
@Override
public void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed) {
realm.setPasswordCredentialGrantAllowed(passwordCredentialGrantAllowed);
em.flush();
}
@Override @Override
public boolean isRegistrationAllowed() { public boolean isRegistrationAllowed() {
return realm.isRegistrationAllowed(); return realm.isRegistrationAllowed();

View file

@ -41,6 +41,7 @@ public class RealmEntity {
protected boolean enabled; protected boolean enabled;
protected boolean sslNotRequired; protected boolean sslNotRequired;
protected boolean registrationAllowed; protected boolean registrationAllowed;
protected boolean passwordCredentialGrantAllowed;
protected boolean verifyEmail; protected boolean verifyEmail;
protected boolean resetPasswordAllowed; protected boolean resetPasswordAllowed;
protected boolean social; protected boolean social;
@ -154,6 +155,14 @@ public class RealmEntity {
this.sslNotRequired = sslNotRequired; this.sslNotRequired = sslNotRequired;
} }
public boolean isPasswordCredentialGrantAllowed() {
return passwordCredentialGrantAllowed;
}
public void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed) {
this.passwordCredentialGrantAllowed = passwordCredentialGrantAllowed;
}
public boolean isRegistrationAllowed() { public boolean isRegistrationAllowed() {
return registrationAllowed; return registrationAllowed;
} }

View file

@ -108,6 +108,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
updateRealm(); updateRealm();
} }
@Override
public boolean isPasswordCredentialGrantAllowed() {
return realm.isPasswordCredentialGrantAllowed();
}
@Override
public void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed) {
realm.setPasswordCredentialGrantAllowed(passwordCredentialGrantAllowed);
updateRealm();
}
@Override @Override
public boolean isRegistrationAllowed() { public boolean isRegistrationAllowed() {
return realm.isRegistrationAllowed(); return realm.isRegistrationAllowed();

View file

@ -81,6 +81,7 @@ public class ModelToRepresentation {
rep.setSslNotRequired(realm.isSslNotRequired()); rep.setSslNotRequired(realm.isSslNotRequired());
rep.setPublicKey(realm.getPublicKeyPem()); rep.setPublicKey(realm.getPublicKeyPem());
rep.setPrivateKey(realm.getPrivateKeyPem()); rep.setPrivateKey(realm.getPrivateKeyPem());
rep.setPasswordCredentialGrantAllowed(realm.isPasswordCredentialGrantAllowed());
rep.setRegistrationAllowed(realm.isRegistrationAllowed()); rep.setRegistrationAllowed(realm.isRegistrationAllowed());
rep.setRememberMe(realm.isRememberMe()); rep.setRememberMe(realm.isRememberMe());
rep.setBruteForceProtected(realm.isBruteForceProtected()); rep.setBruteForceProtected(realm.isBruteForceProtected());

View file

@ -174,6 +174,7 @@ public class RealmManager {
if (rep.getQuickLoginCheckMilliSeconds() != null) realm.setQuickLoginCheckMilliSeconds(rep.getQuickLoginCheckMilliSeconds()); if (rep.getQuickLoginCheckMilliSeconds() != null) realm.setQuickLoginCheckMilliSeconds(rep.getQuickLoginCheckMilliSeconds());
if (rep.getMaxDeltaTimeSeconds() != null) realm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds()); if (rep.getMaxDeltaTimeSeconds() != null) realm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds());
if (rep.getFailureFactor() != null) realm.setFailureFactor(rep.getFailureFactor()); if (rep.getFailureFactor() != null) realm.setFailureFactor(rep.getFailureFactor());
if (rep.isPasswordCredentialGrantAllowed() != null) realm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed());
if (rep.isRegistrationAllowed() != null) realm.setRegistrationAllowed(rep.isRegistrationAllowed()); if (rep.isRegistrationAllowed() != null) realm.setRegistrationAllowed(rep.isRegistrationAllowed());
if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe()); if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe());
if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail()); if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail());
@ -331,6 +332,7 @@ public class RealmManager {
else newRealm.setAccessCodeLifespanUserAction(300); else newRealm.setAccessCodeLifespanUserAction(300);
if (rep.isSslNotRequired() != null) newRealm.setSslNotRequired(rep.isSslNotRequired()); if (rep.isSslNotRequired() != null) newRealm.setSslNotRequired(rep.isSslNotRequired());
if (rep.isPasswordCredentialGrantAllowed() != null) newRealm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed());
if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed()); if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed());
if (rep.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe()); if (rep.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe());
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail()); if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());

View file

@ -205,7 +205,11 @@ public class TokenService {
public Response grantAccessToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, public Response grantAccessToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader,
final MultivaluedMap<String, String> form) { final MultivaluedMap<String, String> form) {
if (!checkSsl()) { if (!checkSsl()) {
throw new NotAcceptableException("HTTPS required"); return createError("https_required", "HTTPS required", Response.Status.FORBIDDEN);
}
if (!realm.isPasswordCredentialGrantAllowed()) {
return createError("not_enabled", "Resource Owner Password Credentials Grant not enabled", Response.Status.FORBIDDEN);
} }
audit.event(Events.LOGIN).detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token"); audit.event(Events.LOGIN).detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
@ -224,12 +228,12 @@ public class TokenService {
if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) { if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
audit.error(Errors.NOT_ALLOWED); audit.error(Errors.NOT_ALLOWED);
throw new ForbiddenException("Bearer-only applications are not allowed to invoke grants/access"); return createError("not_allowed", "Bearer-only applications are not allowed to invoke grants/access", Response.Status.FORBIDDEN);
} }
if (!realm.isEnabled()) { if (!realm.isEnabled()) {
audit.error(Errors.REALM_DISABLED); audit.error(Errors.REALM_DISABLED);
throw new UnauthorizedException("Disabled realm"); return createError("realm_disabled", "Realm is disabled", Response.Status.UNAUTHORIZED);
} }
AuthenticationStatus authenticationStatus = authManager.authenticateForm(clientConnection, realm, form); AuthenticationStatus authenticationStatus = authManager.authenticateForm(clientConnection, realm, form);
@ -1014,4 +1018,13 @@ public class TokenService {
return realm.isSslNotRequired() || uriInfo.getBaseUri().getScheme().equals("https"); return realm.isSslNotRequired() || uriInfo.getBaseUri().getScheme().equals("https");
} }
private Response createError(String error, String errorDescription, Response.Status status) {
Map<String, String> e = new HashMap<String, String>();
e.put(OAuth2Constants.ERROR, error);
if (errorDescription != null) {
e.put(OAuth2Constants.ERROR_DESCRIPTION, errorDescription);
}
return Response.status(status).entity(e).type("application/json").build();
}
} }

View file

@ -404,7 +404,8 @@ public class OAuthClient {
Assert.fail("Invalid content type"); Assert.fail("Invalid content type");
} }
JSONObject responseJson = new JSONObject(IOUtils.toString(response.getEntity().getContent())); String s = IOUtils.toString(response.getEntity().getContent());
JSONObject responseJson = new JSONObject(s);
if (statusCode == 200) { if (statusCode == 200) {
accessToken = responseJson.getString("access_token"); accessToken = responseJson.getString("access_token");

View file

@ -1,21 +1,11 @@
package org.keycloak.testsuite.oauth; package org.keycloak.testsuite.oauth;
import net.iharder.Base64;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.junit.Before;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.audit.Details; import org.keycloak.audit.Details;
import org.keycloak.audit.Errors; import org.keycloak.audit.Errors;
import org.keycloak.audit.Event;
import org.keycloak.models.ApplicationModel; import org.keycloak.models.ApplicationModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@ -28,11 +18,6 @@ import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule; import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
@ -46,6 +31,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ApplicationModel app = appRealm.addApplication("resource-owner"); ApplicationModel app = appRealm.addApplication("resource-owner");
app.setSecret("secret"); app.setSecret("secret");
appRealm.setPasswordCredentialGrantAllowed(true);
} }
}); });
@ -96,6 +82,33 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).client("resource-owner").assertEvent(); events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).client("resource-owner").assertEvent();
} }
@Test
public void grantAccessTokenNotEnabled() throws Exception {
try {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordCredentialGrantAllowed(false);
}
});
oauth.clientId("resource-owner");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "password");
assertEquals(403, response.getStatusCode());
assertEquals("not_enabled", response.getError());
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordCredentialGrantAllowed(true);
}
});
}
}
@Test @Test
public void grantAccessTokenLogout() throws Exception { public void grantAccessTokenLogout() throws Exception {
oauth.clientId("resource-owner"); oauth.clientId("resource-owner");