Merge pull request #404 from stianst/master
Add realm option to enable/disable Resource Owner Password Credentials G...
This commit is contained in:
commit
3a15b416a9
12 changed files with 109 additions and 19 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in a new issue