admin rest api example

This commit is contained in:
Bill Burke 2014-05-23 18:20:55 -04:00
parent 29070cec77
commit e4232c73c6
31 changed files with 556 additions and 36 deletions

View file

@ -10,6 +10,7 @@ public interface ServiceUrlConstants {
public static final String TOKEN_SERVICE_ACCESS_CODE_PATH = "/realms/{realm-name}/tokens/access/codes";
public static final String TOKEN_SERVICE_REFRESH_PATH = "/realms/{realm-name}/tokens/refresh";
public static final String TOKEN_SERVICE_LOGOUT_PATH = "/realms/{realm-name}/tokens/logout";
public static final String TOKEN_SERVICE_DIRECT_GRANT_PATH = "/realms/{realm-name}/tokens/grants/access";
public static final String ACCOUNT_SERVICE_PATH = "/realms/{realm-name}/account";
public static final String REALM_INFO_PATH = "/realms/{realm-name}";

View file

@ -27,6 +27,9 @@ public class AccessTokenResponse {
@JsonProperty("not-before-policy")
protected int notBeforePolicy;
@JsonProperty("session-state")
protected String sessionState;
public String getToken() {
return token;
}
@ -74,4 +77,12 @@ public class AccessTokenResponse {
public void setNotBeforePolicy(int notBeforePolicy) {
this.notBeforePolicy = notBeforePolicy;
}
public String getSessionState() {
return sessionState;
}
public void setSessionState(String sessionState) {
this.sessionState = sessionState;
}
}

View file

@ -17,6 +17,7 @@ public class OAuthClientRepresentation {
protected ClaimRepresentation claims;
protected Integer notBefore;
protected Boolean publicClient;
protected Boolean directGrantsOnly;
public String getId() {
@ -98,4 +99,12 @@ public class OAuthClientRepresentation {
public void setPublicClient(Boolean publicClient) {
this.publicClient = publicClient;
}
public Boolean isDirectGrantsOnly() {
return directGrantsOnly;
}
public void setDirectGrantsOnly(Boolean directGrantsOnly) {
this.directGrantsOnly = directGrantsOnly;
}
}

View file

@ -6,6 +6,7 @@ The following examples requires Wildfly 8.0.0, JBoss EAP 6.x, or JBoss AS 7.1.1.
* Transferring identity and role mappings via a special bearer token (Skeleton Key Token).
* Bearer token authentication and authorization of JAX-RS services
* Obtaining bearer tokens via the OAuth2 protocol
* Interact with the Keycloak Admin REST Api
There are multiple WAR projects. These will all run on the same WildFly instance, but pretend each one is running on a different
machine on the network or Internet.
@ -13,6 +14,7 @@ machine on the network or Internet.
* **customer-app-js** A pure HTML/Javascript application that does remote login using OAuth2 browser redirects with the auth server
* **customer-app-cli** A pure CLI application that does remote login using OAuth2 browser redirects with the auth server
* **product-app** A WAR application that does remote login using OAuth2 browser redirects with the auth server
* **admin-access-app** A WAR application that does remote REST login to admin console to obtain a list of realm roles from Admin REST API
* **database-service** JAX-RS services authenticated by bearer tokens only. The customer and product app invoke on it to get data
* **third-party** Simple WAR that obtain a bearer token using OAuth2 using browser redirects to the auth-server.
* **third-party-cdi** Simple CDI/JSF WAR that obtain a bearer token using OAuth2 using browser redirects to the auth-server.
@ -184,6 +186,16 @@ The CLI example has two alternative methods for login. When a browser is availab
temporary web server on a free port. If a browser is not available the URL to login is displayed on the CLI. The user can copy this URL to another computer that has a browser available. The code
is displayed to the user after login and the user has to copy this code back to the application.
Step 8: Admin REST API
----------------------------------
Keycloak has a Admin REST API. This example shows an application making a remove direct login to Keycloak to obtain a token
then using that token to access the Admin REST API.
[http://localhost:8080/admin-access](http://localhost:8080/admin-access)
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
Admin Console
==========================

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-beta-1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.keycloak.example.demo</groupId>
<artifactId>admin-access-example</artifactId>
<packaging>war</packaging>
<name>Admin Access Example</name>
<description/>
<repositories>
<repository>
<id>jboss</id>
<name>jboss repo</name>
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${keycloak.apache.httpcomponents.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>admin-access</finalName>
<plugins>
<plugin>
<groupId>org.jboss.as.plugins</groupId>
<artifactId>jboss-as-maven-plugin</artifactId>
<version>7.4.Final</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,151 @@
package org.keycloak.example;
import org.apache.http.HttpEntity;
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.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.OAuth2Constants;
import org.keycloak.ServiceUrlConstants;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.KeycloakUriBuilder;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AdminClient {
static class TypedList extends ArrayList<RoleRepresentation> {
}
public static class Failure extends Exception {
private int status;
public Failure(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
}
public static AccessTokenResponse getToken() throws IOException {
HttpClient client = new HttpClientBuilder()
.disableTrustManager().build();
try {
HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri("http://localhost:8080/auth")
.path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build("demo"));
List <NameValuePair> formparams = new ArrayList <NameValuePair>();
formparams.add(new BasicNameValuePair("username", "admin"));
formparams.add(new BasicNameValuePair("password", "password"));
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "admin-client"));
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
throw new IOException("Bad status: " + status);
}
if (entity == null) {
throw new IOException("No Entity");
}
InputStream is = entity.getContent();
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
String json = new String(bytes);
try {
return JsonSerialization.readValue(json, AccessTokenResponse.class);
} catch (IOException e) {
throw new IOException(json, e);
}
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
} finally {
client.getConnectionManager().shutdown();
}
}
public static void logout(AccessTokenResponse res) throws IOException {
HttpClient client = new HttpClientBuilder()
.disableTrustManager().build();
try {
HttpGet get = new HttpGet(KeycloakUriBuilder.fromUri("http://localhost:8080/auth")
.path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH)
.queryParam("session-state", res.getSessionState())
.build("demo"));
HttpResponse response = client.execute(get);
HttpEntity entity = response.getEntity();
if (entity == null) {
return;
}
InputStream is = entity.getContent();
if (is != null) is.close();
} finally {
client.getConnectionManager().shutdown();
}
}
public static List<RoleRepresentation> getRealmRoles(AccessTokenResponse res) throws Failure {
HttpClient client = new HttpClientBuilder()
.disableTrustManager().build();
try {
HttpGet get = new HttpGet("http://localhost:8080/auth/admin/realms/demo/roles");
get.addHeader("Authorization", "Bearer " + res.getToken());
try {
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() != 200) {
throw new Failure(response.getStatusLine().getStatusCode());
}
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
try {
return JsonSerialization.readValue(is, TypedList.class);
} finally {
is.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} finally {
client.getConnectionManager().shutdown();
}
}
}

View file

@ -0,0 +1,9 @@
<jboss-deployment-structure>
<deployment>
<dependencies>
<!-- the Demo code uses classes in these modules. These are optional to import if you are not using
Apache Http Client or the HttpClientBuilder that comes with the adapter core -->
<module name="org.apache.httpcomponents"/>
</dependencies>
</deployment>
</jboss-deployment-structure>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>admin-access</module-name>
</web-app>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>admin-access</module-name>
</web-app>

View file

@ -0,0 +1,30 @@
<%@ page import="org.keycloak.representations.idm.RoleRepresentation" %>
<%@ page import="org.keycloak.example.AdminClient" %>
<%@ page import="org.keycloak.representations.AccessTokenResponse" %>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1" %>
<html>
<head>
<title>Admin Interface</title>
</head>
<body bgcolor="#E3F6CE">
<h2>List of Realm Roles from Admin REST API Call</h2>
<%
java.util.List<RoleRepresentation> list = null;
try {
AccessTokenResponse res = AdminClient.getToken();
list = AdminClient.getRealmRoles(res);
AdminClient.logout(res);
} catch (AdminClient.Failure failure) {
out.println("There was a failure processing request. You either didn't configure Keycloak properly");
out.println("Status from database service invocation was: " + failure.getStatus());
return;
}
for (RoleRepresentation role : list) {
out.print("<p>");
out.print(role.getName());
out.println("</p>");
}
%></body>
</html>

View file

@ -0,0 +1,67 @@
package org.keycloak.example;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.util.JsonSerialization;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AdminClient {
static class TypedList extends ArrayList<RoleRepresentation> {
}
public static class Failure extends Exception {
private int status;
public Failure(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
}
public static List<RoleRepresentation> getRealmRoles(HttpServletRequest req) throws Failure {
KeycloakSecurityContext session = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
HttpClient client = new HttpClientBuilder()
.disableTrustManager().build();
try {
HttpGet get = new HttpGet("http://localhost:8080/auth/admin/realms/demo/roles");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
try {
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() != 200) {
throw new Failure(response.getStatusLine().getStatusCode());
}
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
try {
return JsonSerialization.readValue(is, TypedList.class);
} finally {
is.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} finally {
client.getConnectionManager().shutdown();
}
}
}

View file

@ -1,3 +1,5 @@
<%@ page import="org.keycloak.representations.idm.RoleRepresentation" %>
<%@ page import="org.keycloak.example.AdminClient" %>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1" %>
<html>
@ -8,5 +10,24 @@
<h1>Customer Admin Interface</h1>
User <b><%=request.getUserPrincipal().getName()%>
</b> made this request.
</body>
<p>
</p>
<h2>Admin REST To Get Role List of Realm</h2>
<%
java.util.List<RoleRepresentation> list = null;
try {
list = AdminClient.getRealmRoles(request);
} catch (AdminClient.Failure failure) {
out.println("There was a failure processing request. You either didn't configure Keycloak properly");
out.println("Status from database service invocation was: " + failure.getStatus());
return;
}
for (RoleRepresentation role : list) {
out.print("<p>");
out.print(role.getName());
out.println("</p>");
}
%></body>
</html>

View file

@ -38,6 +38,7 @@
<module>customer-app-cli</module>
<module>customer-app-js</module>
<module>product-app</module>
<module>admin-access-app</module>
<module>angular-product-app</module>
<module>database-service</module>
<module>third-party</module>

View file

@ -4,6 +4,7 @@
"accessTokenLifespan": 3000,
"accessCodeLifespan": 10,
"accessCodeLifespanUserAction": 6000,
"passwordCredentialGrantAllowed": true,
"sslNotRequired": true,
"registrationAllowed": false,
"social": false,
@ -22,6 +23,17 @@
{ "type" : "password",
"value" : "password" }
]
},
{
"username" : "admin",
"enabled": true,
"email" : "admin@admin.com",
"firstName": "Admin",
"lastName": "Burke",
"credentials" : [
{ "type" : "password",
"value" : "password" }
]
}
],
"roles" : {
@ -40,6 +52,10 @@
{
"username": "bburke@redhat.com",
"roles": ["user"]
},
{
"username": "admin",
"roles": ["user","admin"]
}
],
"scopeMappings": [
@ -123,6 +139,13 @@
"http://localhost:8080/oauth-client-cdi/*"
],
"secret": "password"
},
{
"name": "admin-client",
"enabled": true,
"publicClient": true,
"directGrantsOnly": true
}
],
"applicationRoleMappings": {
@ -131,7 +154,22 @@
"username": "bburke@redhat.com",
"roles": ["manage-account"]
}
],
"realm-management": [
{
"username": "admin",
"roles": ["realm-admin"]
}
]
},
"applicationScopeMappings": {
"realm-management": [
{
"client": "admin-client",
"roles": ["realm-admin"]
}
]
}
}

View file

@ -198,7 +198,7 @@ module.controller('ApplicationInstallationCtrl', function($scope, realm, applica
module.controller('ApplicationDetailCtrl', function($scope, realm, application, Application, $location, Dialog, Notifications) {
console.log('ApplicationDetailCtrl');
$scope.clientTypes = [
$scope.accessTypes = [
"confidential",
"public",
"bearer-only"
@ -208,28 +208,27 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application,
$scope.create = !application.name;
if (!$scope.create) {
$scope.application= angular.copy(application);
$scope.clientType = $scope.clientTypes[0];
$scope.accessType = $scope.accessTypes[0];
if (application.bearerOnly) {
$scope.clientType = $scope.clientTypes[2];
$scope.accessType = $scope.accessTypes[2];
} else if (application.publicClient) {
$scope.clientType = $scope.clientTypes[1];
$scope.accessType = $scope.accessTypes[1];
}
} else {
$scope.application = { enabled: true };
$scope.application.webOrigins = [];
$scope.application.redirectUris = [];
$scope.clientType = $scope.clientTypes[0];
$scope.accessType = $scope.accessTypes[0];
}
$scope.changeClientType = function() {
console.log('Client Type: ' + $scope.clientType);
if ($scope.clientType == "confidential") {
$scope.changeAccessType = function() {
if ($scope.accessType == "confidential") {
$scope.application.bearerOnly = false;
$scope.application.publicClient = false;
} else if ($scope.clientType == "public") {
} else if ($scope.accessType == "public") {
$scope.application.bearerOnly = false;
$scope.application.publicClient = true;
} else if ($scope.clientType == "bearer-only") {
} else if ($scope.accessType == "bearer-only") {
$scope.application.bearerOnly = true;
$scope.application.publicClient = false;
}

View file

@ -77,16 +77,15 @@ module.controller('OAuthClientDetailCtrl', function($scope, realm, oauth, OAuthC
$scope.realm = realm;
$scope.create = !oauth.id;
$scope.clientTypes = [
$scope.accessTypes = [
"confidential",
"public"
];
$scope.changeClientType = function() {
console.log('Client Type: ' + $scope.clientType);
if ($scope.clientType == "confidential") {
$scope.changeAccessType = function() {
if ($scope.accessType == "confidential") {
$scope.oauth.publicClient = false;
} else if ($scope.clientType == "public") {
} else if ($scope.accessType == "public") {
$scope.oauth.publicClient = true;
}
};
@ -94,15 +93,15 @@ module.controller('OAuthClientDetailCtrl', function($scope, realm, oauth, OAuthC
if (!$scope.create) {
$scope.oauth= angular.copy(oauth);
$scope.clientType = $scope.clientTypes[0];
$scope.accessType = $scope.accessTypes[0];
if (oauth.publicClient) {
$scope.clientType = $scope.clientTypes[1];
$scope.accessType = $scope.accessTypes[1];
}
} else {
$scope.oauth = { enabled: true };
$scope.oauth.webOrigins = [];
$scope.oauth.redirectUris = [];
$scope.clientType = $scope.clientTypes[0];
$scope.accessType = $scope.accessTypes[0];
}
$scope.$watch(function() {
@ -133,7 +132,7 @@ module.controller('OAuthClientDetailCtrl', function($scope, realm, oauth, OAuthC
}
$scope.save = function() {
if (!$scope.oauth.redirectUris || $scope.oauth.redirectUris.length == 0) {
if (!$scope.oauth.directGrantsOnly && (!$scope.oauth.redirectUris || $scope.oauth.redirectUris.length == 0)) {
Notifications.error("You must specify at least one redirect uri");
} else {
if ($scope.create) {

View file

@ -42,13 +42,13 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="clientType">Client Type</label>
<label class="col-sm-2 control-label" for="accessType">Access Type</label>
<div class="col-sm-4">
<div class="select-kc">
<select id="clientType"
ng-change="changeClientType()"
ng-model="clientType"
ng-options="cType for cType in clientTypes">
<select id="accessType"
ng-change="changeAccessType()"
ng-model="accessType"
ng-options="aType for aType in accessTypes">
</select>
</div>
</div>

View file

@ -41,18 +41,24 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="clientType">Client Type</label>
<label class="col-sm-2 control-label" for="accessType">Access Type</label>
<div class="col-sm-4">
<div class="select-kc">
<select id="clientType"
ng-change="changeClientType()"
ng-model="clientType"
ng-options="cType for cType in clientTypes">
<select id="accessType"
ng-change="changeAccessType()"
ng-model="accessType"
ng-options="aType for aType in accessTypes">
</select>
</div>
</div>
</div>
<div class="form-group">
<div class="form-group clearfix block">
<label class="col-sm-2 control-label" for="directGrantsOnly">Direct Grants Only</label>
<div class="col-sm-4">
<input ng-model="oauth.directGrantsOnly" name="directGrantsOnly" id="directGrantsOnly" onoffswitch />
</div>
</div>
<div class="form-group" data-ng-hide="oauth.directGrantsOnly">
<label class="col-sm-2 control-label" for="newRedirectUri">Redirect URI <span class="required" data-ng-show="create">*</span></label>
<div class="col-sm-4 multiple" ng-repeat="redirectUri in oauth.redirectUris">
<div class="input-group kc-item-deletable">
@ -75,7 +81,7 @@
</div>
</div>
</div>
<div class="form-group">
<div class="form-group" data-ng-hide="create">
<label class="col-sm-2 control-label" for="newWebOrigin">Web Origin</label>
<div class="col-sm-4 multiple" ng-repeat="webOrigin in oauth.webOrigins">
<div class="input-group kc-item-deletable">

View file

@ -67,7 +67,7 @@
</div>
</div>
<div class="form-group">
<label for="passwordCredentialGrantAllowed" class="col-sm-2 control-label">Password Credential Grant</label>
<label for="passwordCredentialGrantAllowed" class="col-sm-2 control-label">Direct Grant API</label>
<div class="col-sm-4">
<input ng-model="realm.passwordCredentialGrantAllowedpasswordCredentialGrantAllowed" name="passwordCredentialGrantAllowed" id="passwordCredentialGrantAllowed" onoffswitch />
</div>

View file

@ -54,6 +54,9 @@ public interface ClientModel {
boolean isPublicClient();
void setPublicClient(boolean flag);
boolean isDirectGrantsOnly();
void setDirectGrantsOnly(boolean flag);
RealmModel getRealm();
/**

View file

@ -4,4 +4,13 @@ package org.keycloak.models.entities;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OAuthClientEntity extends ClientEntity {
protected boolean directGrantsOnly;
public boolean isDirectGrantsOnly() {
return directGrantsOnly;
}
public void setDirectGrantsOnly(boolean directGrantsOnly) {
this.directGrantsOnly = directGrantsOnly;
}
}

View file

@ -88,6 +88,16 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
applicationEntity.setBearerOnly(only);
}
@Override
public boolean isDirectGrantsOnly() {
return false; // applications can't be grant only
}
@Override
public void setDirectGrantsOnly(boolean flag) {
// applications can't be grant only
}
@Override
public RoleModel getRole(String name) {
TypedQuery<ApplicationRoleEntity> query = em.createNamedQuery("getAppRoleByName", ApplicationRoleEntity.class);

View file

@ -18,7 +18,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ClientAdapter implements ClientModel {
public abstract class ClientAdapter implements ClientModel {
protected ClientEntity entity;
protected RealmModel realm;
protected EntityManager em;

View file

@ -15,8 +15,11 @@ import java.util.Set;
*/
public class OAuthClientAdapter extends ClientAdapter implements OAuthClientModel {
protected final OAuthClientEntity oAuthClientEntity;
public OAuthClientAdapter(RealmModel realm, OAuthClientEntity entity, EntityManager em) {
super(realm, entity, em);
oAuthClientEntity = entity;
}
@Override
@ -24,4 +27,14 @@ public class OAuthClientAdapter extends ClientAdapter implements OAuthClientMode
entity.setName(id);
}
@Override
public boolean isDirectGrantsOnly() {
return oAuthClientEntity.isDirectGrantsOnly();
}
@Override
public void setDirectGrantsOnly(boolean flag) {
oAuthClientEntity.setDirectGrantsOnly(flag);
}
}

View file

@ -15,4 +15,13 @@ import javax.persistence.NamedQuery;
})
@Entity
public class OAuthClientEntity extends ClientEntity {
protected boolean directGrantsOnly;
public boolean isDirectGrantsOnly() {
return directGrantsOnly;
}
public void setDirectGrantsOnly(boolean directGrantsOnly) {
this.directGrantsOnly = directGrantsOnly;
}
}

View file

@ -97,6 +97,17 @@ public class ApplicationAdapter extends ClientAdapter<MongoApplicationEntity> im
updateMongoEntity();
}
@Override
public boolean isDirectGrantsOnly() {
return false; // applications can't be grant only
}
@Override
public void setDirectGrantsOnly(boolean flag) {
// applications can't be grant only
}
@Override
public RoleAdapter getRole(String name) {
DBObject query = new QueryBuilder()

View file

@ -18,7 +18,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMongoAdapter<T> implements ClientModel {
public abstract class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMongoAdapter<T> implements ClientModel {
protected final T clientEntity;
private final RealmAdapter realm;

View file

@ -19,4 +19,14 @@ public class OAuthClientAdapter extends ClientAdapter<MongoOAuthClientEntity> im
getMongoEntity().setName(id);
updateMongoEntity();
}
@Override
public boolean isDirectGrantsOnly() {
return getMongoEntity().isDirectGrantsOnly();
}
@Override
public void setDirectGrantsOnly(boolean flag) {
getMongoEntity().setDirectGrantsOnly(flag);
}
}

View file

@ -51,6 +51,7 @@ public class OAuthClientManager {
if (rep.getName() != null) model.setClientId(rep.getName());
if (rep.isEnabled() != null) model.setEnabled(rep.isEnabled());
if (rep.isPublicClient() != null) model.setPublicClient(rep.isPublicClient());
if (rep.isDirectGrantsOnly() != null) model.setDirectGrantsOnly(rep.isDirectGrantsOnly());
if (rep.getClaims() != null) {
ClaimManager.setClaims(model, rep.getClaims());
}
@ -84,6 +85,7 @@ public class OAuthClientManager {
rep.setName(model.getClientId());
rep.setEnabled(model.isEnabled());
rep.setPublicClient(model.isPublicClient());
rep.setDirectGrantsOnly(model.isDirectGrantsOnly());
Set<String> redirectUris = model.getRedirectUris();
if (redirectUris != null) {
rep.setRedirectUris(new LinkedList<String>(redirectUris));

View file

@ -453,6 +453,7 @@ public class TokenManager {
String encodedToken = new JWSBuilder().jsonContent(accessToken).rsa256(realm.getPrivateKey());
res.setToken(encodedToken);
res.setTokenType("bearer");
res.setSessionState(accessToken.getSessionState());
if (accessToken.getExpiration() != 0) {
res.setExpiresIn(accessToken.getExpiration() - Time.currentTime());
}

View file

@ -754,7 +754,11 @@ public class TokenService {
}
if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
audit.error(Errors.NOT_ALLOWED);
return oauth.forwardToSecurityFailure("Bearer-only applications are not allowed to initiate login");
return oauth.forwardToSecurityFailure("Bearer-only applications are not allowed to initiate browser login");
}
if (client.isDirectGrantsOnly()) {
audit.error(Errors.NOT_ALLOWED);
return oauth.forwardToSecurityFailure("direct-grants-only clients are not allowed to initiate browser login");
}
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {