KEYCLOAK-1868 Import clients through admin console
KEYCLOAK-1869 Add root url to clients that should be used to resolve relative urls
This commit is contained in:
parent
8b6251eb23
commit
55deedd3b8
54 changed files with 934 additions and 413 deletions
|
@ -8,6 +8,10 @@
|
|||
</column>
|
||||
</addColumn>
|
||||
|
||||
<addColumn tableName="CLIENT">
|
||||
<column name="ROOT_URL" type="VARCHAR(255)"/>
|
||||
</addColumn>
|
||||
|
||||
<createTable tableName="OFFLINE_USER_SESSION">
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
|
|
|
@ -11,6 +11,7 @@ public class ClientRepresentation {
|
|||
protected String id;
|
||||
protected String clientId;
|
||||
protected String name;
|
||||
protected String rootUrl;
|
||||
protected String adminUrl;
|
||||
protected String baseUrl;
|
||||
protected Boolean surrogateAuthRequired;
|
||||
|
@ -74,6 +75,14 @@ public class ClientRepresentation {
|
|||
this.surrogateAuthRequired = surrogateAuthRequired;
|
||||
}
|
||||
|
||||
public String getRootUrl() {
|
||||
return rootUrl;
|
||||
}
|
||||
|
||||
public void setRootUrl(String rootUrl) {
|
||||
this.rootUrl = rootUrl;
|
||||
}
|
||||
|
||||
public String getAdminUrl() {
|
||||
return adminUrl;
|
||||
}
|
||||
|
|
|
@ -564,8 +564,6 @@ module.controller('ClientRoleDetailCtrl', function($scope, realm, client, role,
|
|||
module.controller('ClientImportCtrl', function($scope, $location, $upload, realm, serverInfo, Notifications) {
|
||||
|
||||
$scope.realm = realm;
|
||||
$scope.configFormats = serverInfo.clientImporters;
|
||||
$scope.configFormat = null;
|
||||
|
||||
$scope.files = [];
|
||||
|
||||
|
@ -614,7 +612,6 @@ module.controller('ClientImportCtrl', function($scope, $location, $upload, realm
|
|||
module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.clients = clients;
|
||||
$scope.importButton = serverInfo.clientImporters.length > 0;
|
||||
|
||||
$scope.removeClient = function(client) {
|
||||
Dialog.confirmDelete(client.clientId, 'client', function() {
|
||||
|
@ -670,7 +667,7 @@ module.controller('ClientInstallationCtrl', function($scope, realm, client, Clie
|
|||
}
|
||||
});
|
||||
|
||||
module.controller('ClientDetailCtrl', function($scope, realm, client, $route, serverInfo, Client, $location, Dialog, Notifications) {
|
||||
module.controller('ClientDetailCtrl', function($scope, realm, client, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) {
|
||||
$scope.accessTypes = [
|
||||
"confidential",
|
||||
"public",
|
||||
|
@ -709,40 +706,45 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
|
|||
$scope.samlEncrypt = false;
|
||||
$scope.samlForcePostBinding = false;
|
||||
$scope.samlForceNameIdFormat = false;
|
||||
if (!$scope.create) {
|
||||
if (!client.attributes) {
|
||||
client.attributes = {};
|
||||
|
||||
function updateProperties() {
|
||||
if (!$scope.client.attributes) {
|
||||
$scope.client.attributes = {};
|
||||
}
|
||||
$scope.client= angular.copy(client);
|
||||
$scope.accessType = $scope.accessTypes[0];
|
||||
if (client.bearerOnly) {
|
||||
if ($scope.client.bearerOnly) {
|
||||
$scope.accessType = $scope.accessTypes[2];
|
||||
} else if (client.publicClient) {
|
||||
} else if ($scope.client.publicClient) {
|
||||
$scope.accessType = $scope.accessTypes[1];
|
||||
}
|
||||
if (client.protocol) {
|
||||
$scope.protocol = $scope.protocols[$scope.protocols.indexOf(client.protocol)];
|
||||
if ($scope.client.protocol) {
|
||||
$scope.protocol = $scope.protocols[$scope.protocols.indexOf($scope.client.protocol)];
|
||||
} else {
|
||||
$scope.protocol = $scope.protocols[0];
|
||||
}
|
||||
if (client.attributes['saml.signature.algorithm'] == 'RSA_SHA1') {
|
||||
if ($scope.client.attributes['saml.signature.algorithm'] == 'RSA_SHA1') {
|
||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[0];
|
||||
} else if (client.attributes['saml.signature.algorithm'] == 'RSA_SHA256') {
|
||||
} else if ($scope.client.attributes['saml.signature.algorithm'] == 'RSA_SHA256') {
|
||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[1];
|
||||
} else if (client.attributes['saml.signature.algorithm'] == 'RSA_SHA512') {
|
||||
} else if ($scope.client.attributes['saml.signature.algorithm'] == 'RSA_SHA512') {
|
||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[2];
|
||||
} else if (client.attributes['saml.signature.algorithm'] == 'DSA_SHA1') {
|
||||
} else if ($scope.client.attributes['saml.signature.algorithm'] == 'DSA_SHA1') {
|
||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[3];
|
||||
}
|
||||
if (client.attributes['saml_name_id_format'] == 'unspecified') {
|
||||
if ($scope.client.attributes['saml_name_id_format'] == 'unspecified') {
|
||||
$scope.nameIdFormat = $scope.nameIdFormats[0];
|
||||
} else if (client.attributes['saml_name_id_format'] == 'email') {
|
||||
} else if ($scope.client.attributes['saml_name_id_format'] == 'email') {
|
||||
$scope.nameIdFormat = $scope.nameIdFormats[1];
|
||||
} else if (client.attributes['saml_name_id_format'] == 'transient') {
|
||||
} else if ($scope.client.attributes['saml_name_id_format'] == 'transient') {
|
||||
$scope.nameIdFormat = $scope.nameIdFormats[2];
|
||||
} else if (client.attributes['saml_name_id_format'] == 'persistent') {
|
||||
} else if ($scope.client.attributes['saml_name_id_format'] == 'persistent') {
|
||||
$scope.nameIdFormat = $scope.nameIdFormats[3];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$scope.create) {
|
||||
$scope.client = angular.copy(client);
|
||||
updateProperties();
|
||||
} else {
|
||||
$scope.client = { enabled: true, attributes: {}};
|
||||
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
|
||||
|
@ -813,6 +815,29 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
|
|||
}
|
||||
}
|
||||
|
||||
$scope.importFile = function(fileContent){
|
||||
console.debug(fileContent);
|
||||
ClientDescriptionConverter.save({
|
||||
realm: realm.realm
|
||||
}, fileContent, function (data) {
|
||||
$scope.client = data;
|
||||
updateProperties();
|
||||
$scope.importing = true;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.viewImportDetails = function() {
|
||||
$modal.open({
|
||||
templateUrl: resourceUrl + '/partials/modal/view-object.html',
|
||||
controller: 'JsonModalCtrl',
|
||||
resolve: {
|
||||
object: function () {
|
||||
return $scope.client;
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
$scope.switchChange = function() {
|
||||
$scope.changed = true;
|
||||
}
|
||||
|
|
|
@ -927,6 +927,13 @@ module.factory('Client', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ClientDescriptionConverter', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/client-description-converter', {
|
||||
realm : '@realm'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.factory('ClientInstallation', function($resource) {
|
||||
var url = authUrl + '/admin/realms/:realm/clients/:client/installation/json';
|
||||
return {
|
||||
|
|
|
@ -10,6 +10,20 @@
|
|||
|
||||
<form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group" data-ng-show="create">
|
||||
<label for="name" class="col-sm-2 control-label">Import</label>
|
||||
|
||||
<div class="col-md-6" data-ng-hide="importing">
|
||||
<label for="import-file" class="btn btn-default">Select file <i class="pficon pficon-import"></i></label>
|
||||
<input id="import-file" type="file" class="hidden" kc-on-read-file="importFile($fileContent)">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" data-ng-show="importing">
|
||||
<button class="btn btn-default" data-ng-click="viewImportDetails()">View details</button>
|
||||
<button class="btn btn-default" data-ng-click="reset()">Clear import</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="clientId">Client ID <span class="required" data-ng-show="create">*</span></label>
|
||||
<div class="col-sm-6">
|
||||
|
@ -174,6 +188,14 @@
|
|||
<kc-tooltip>The name ID format to use for the subject.</kc-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="form-group" data-ng-show="!client.bearerOnly">
|
||||
<label class="col-md-2 control-label" for="rootUrl">Root URL</label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="text" name="rootUrl" id="rootUrl" data-ng-model="client.rootUrl">
|
||||
</div>
|
||||
<kc-tooltip>Root URL appended to relative URLs</kc-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="form-group clearfix block" data-ng-hide="client.bearerOnly || client.directGrantsOnly">
|
||||
<label class="col-md-2 control-label" for="newRedirectUri"><span class="required" data-ng-show="protocol != 'saml'">*</span> Valid Redirect URIs</label>
|
||||
|
||||
|
@ -252,7 +274,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<kc-tooltip>Allowed CORS origins.</kc-tooltip>
|
||||
<kc-tooltip>Allowed CORS origins. To permit all origins of Valid Redirect URIs add '+'. To permit all origins add '*'.</kc-tooltip>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset data-ng-show="protocol == 'saml'">
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></td>
|
||||
<td>{{client.enabled}}</td>
|
||||
<td ng-class="{'text-muted': !client.baseUrl}">
|
||||
<a href="{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.baseUrl}}</a>
|
||||
<a href="{{client.rootUrl}}{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.rootUrl}}{{client.baseUrl}}</a>
|
||||
<span data-ng-hide="client.baseUrl">Not defined</span>
|
||||
</td>
|
||||
<td class="kc-action-cell">
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
|
||||
<h1>Add Realm</h1>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<legend><span class="text">Create Realm</span></legend>
|
||||
<div class="form-group">
|
||||
<label for="name" class="col-sm-2 control-label">Import</label>
|
||||
|
||||
|
|
|
@ -241,7 +241,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
}
|
||||
|
||||
if (client != null) {
|
||||
attributes.put("client", new ClientBean(client));
|
||||
attributes.put("client", new ClientBean(client, baseUri));
|
||||
}
|
||||
|
||||
attributes.put("login", new LoginBean(formData));
|
||||
|
@ -322,7 +322,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
logger.warn("Failed to load properties", e);
|
||||
}
|
||||
if (client != null) {
|
||||
attributes.put("client", new ClientBean(client));
|
||||
attributes.put("client", new ClientBean(client, baseUri));
|
||||
}
|
||||
|
||||
Properties messagesBundle;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package org.keycloak.login.freemarker.model;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.services.util.ResolveRelative;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -10,8 +13,11 @@ public class ClientBean {
|
|||
|
||||
protected ClientModel client;
|
||||
|
||||
public ClientBean(ClientModel client) {
|
||||
private URI requestUri;
|
||||
|
||||
public ClientBean(ClientModel client, URI requestUri) {
|
||||
this.client = client;
|
||||
this.requestUri = requestUri;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
|
@ -23,7 +29,7 @@ public class ClientBean {
|
|||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return client.getBaseUrl();
|
||||
return ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), client.getBaseUrl());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
package org.keycloak.admin.client.resource;
|
||||
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -28,6 +24,12 @@ public interface RealmResource {
|
|||
@Path("clients")
|
||||
ClientsResource clients();
|
||||
|
||||
@Path("client-description-converter")
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
ClientRepresentation convertClientDescription(String description);
|
||||
|
||||
@Path("users")
|
||||
UsersResource users();
|
||||
|
||||
|
|
|
@ -56,6 +56,10 @@ public interface ClientModel extends RoleContainerModel {
|
|||
|
||||
void setManagementUrl(String url);
|
||||
|
||||
String getRootUrl();
|
||||
|
||||
void setRootUrl(String url);
|
||||
|
||||
String getBaseUrl();
|
||||
|
||||
void setBaseUrl(String url);
|
||||
|
|
|
@ -24,6 +24,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
|||
|
||||
private boolean surrogateAuthRequired;
|
||||
private String managementUrl;
|
||||
private String rootUrl;
|
||||
private String baseUrl;
|
||||
private boolean bearerOnly;
|
||||
private boolean consentRequired;
|
||||
|
@ -196,6 +197,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
|||
this.managementUrl = managementUrl;
|
||||
}
|
||||
|
||||
public String getRootUrl() {
|
||||
return rootUrl;
|
||||
}
|
||||
|
||||
public void setRootUrl(String rootUrl) {
|
||||
this.rootUrl = rootUrl;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
|
|
@ -306,6 +306,7 @@ public class ModelToRepresentation {
|
|||
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
|
||||
rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
|
||||
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
|
||||
rep.setRootUrl(clientModel.getRootUrl());
|
||||
rep.setBaseUrl(clientModel.getBaseUrl());
|
||||
rep.setNotBefore(clientModel.getNotBefore());
|
||||
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
|
||||
|
|
|
@ -692,6 +692,7 @@ public class RepresentationToModel {
|
|||
client.setManagementUrl(resourceRep.getAdminUrl());
|
||||
if (resourceRep.isSurrogateAuthRequired() != null)
|
||||
client.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
|
||||
if (resourceRep.getRootUrl() != null) client.setRootUrl(resourceRep.getRootUrl());
|
||||
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
|
||||
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
|
||||
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
|
||||
|
@ -796,6 +797,7 @@ public class RepresentationToModel {
|
|||
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
|
||||
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
|
||||
if (rep.isFrontchannelLogout() != null) resource.setFrontchannelLogout(rep.isFrontchannelLogout());
|
||||
if (rep.getRootUrl() != null) resource.setRootUrl(rep.getRootUrl());
|
||||
if (rep.getAdminUrl() != null) resource.setManagementUrl(rep.getAdminUrl());
|
||||
if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl());
|
||||
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
|
||||
|
|
|
@ -421,6 +421,16 @@ public class ClientAdapter implements ClientModel {
|
|||
entity.setManagementUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootUrl(String url) {
|
||||
entity.setRootUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
return entity.getRootUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBaseUrl(String url) {
|
||||
entity.setBaseUrl(url);
|
||||
|
|
|
@ -345,6 +345,18 @@ public class ClientAdapter implements ClientModel {
|
|||
updated.setManagementUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
if (updated != null) return updated.getRootUrl();
|
||||
return cached.getRootUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootUrl(String url) {
|
||||
getDelegateForUpdate();
|
||||
updated.setRootUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
if (updated != null) return updated.getBaseUrl();
|
||||
|
|
|
@ -42,6 +42,7 @@ public class CachedClient implements Serializable {
|
|||
private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
|
||||
private boolean surrogateAuthRequired;
|
||||
private String managementUrl;
|
||||
private String rootUrl;
|
||||
private String baseUrl;
|
||||
private List<String> defaultRoles = new LinkedList<String>();
|
||||
private boolean bearerOnly;
|
||||
|
@ -76,6 +77,7 @@ public class CachedClient implements Serializable {
|
|||
}
|
||||
surrogateAuthRequired = model.isSurrogateAuthRequired();
|
||||
managementUrl = model.getManagementUrl();
|
||||
rootUrl = model.getRootUrl();
|
||||
baseUrl = model.getBaseUrl();
|
||||
defaultRoles.addAll(model.getDefaultRoles());
|
||||
bearerOnly = model.isBearerOnly();
|
||||
|
@ -169,6 +171,10 @@ public class CachedClient implements Serializable {
|
|||
return managementUrl;
|
||||
}
|
||||
|
||||
public String getRootUrl() {
|
||||
return rootUrl;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
|
|
@ -441,6 +441,16 @@ public class ClientAdapter implements ClientModel {
|
|||
entity.setManagementUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
return entity.getRootUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootUrl(String url) {
|
||||
entity.setRootUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return entity.getBaseUrl();
|
||||
|
|
|
@ -82,6 +82,9 @@ public class ClientEntity {
|
|||
@Column(name="SURROGATE_AUTH_REQUIRED")
|
||||
private boolean surrogateAuthRequired;
|
||||
|
||||
@Column(name="ROOT_URL")
|
||||
private String rootUrl;
|
||||
|
||||
@Column(name="BASE_URL")
|
||||
private String baseUrl;
|
||||
|
||||
|
@ -260,6 +263,14 @@ public class ClientEntity {
|
|||
this.surrogateAuthRequired = surrogateAuthRequired;
|
||||
}
|
||||
|
||||
public String getRootUrl() {
|
||||
return rootUrl;
|
||||
}
|
||||
|
||||
public void setRootUrl(String rootUrl) {
|
||||
this.rootUrl = rootUrl;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
|
|
@ -439,6 +439,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
|
|||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootUrl(String url) {
|
||||
getMongoEntity().setRootUrl(url);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
return getMongoEntity().getRootUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBaseUrl(String url) {
|
||||
getMongoEntity().setBaseUrl(url);
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.dom.saml.v2.metadata.*;
|
||||
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.saml.SignatureAlgorithm;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
||||
import org.keycloak.saml.processing.core.saml.v2.util.SAMLMetadataUtil;
|
||||
import org.keycloak.saml.processing.core.util.CoreConfigUtil;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
|
||||
|
||||
@Override
|
||||
public boolean isSupported(String description) {
|
||||
description = description.trim();
|
||||
return (description.startsWith("<") && description.endsWith(">") && description.contains("EntityDescriptor"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRepresentation convertToInternal(String description) {
|
||||
return loadEntityDescriptors(new ByteArrayInputStream(description.getBytes()));
|
||||
}
|
||||
|
||||
private static ClientRepresentation loadEntityDescriptors(InputStream is) {
|
||||
Object metadata;
|
||||
try {
|
||||
metadata = new SAMLParser().parse(is);
|
||||
} catch (ParsingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
EntitiesDescriptorType entities;
|
||||
|
||||
if (EntitiesDescriptorType.class.isInstance(metadata)) {
|
||||
entities = (EntitiesDescriptorType) metadata;
|
||||
} else {
|
||||
entities = new EntitiesDescriptorType();
|
||||
entities.addEntityDescriptor(metadata);
|
||||
}
|
||||
|
||||
if (entities.getEntityDescriptor().size() != 1) {
|
||||
throw new RuntimeException("Expected one entity descriptor");
|
||||
}
|
||||
|
||||
EntityDescriptorType entity = (EntityDescriptorType) entities.getEntityDescriptor().get(0);
|
||||
String entityId = entity.getEntityID();
|
||||
|
||||
ClientRepresentation app = new ClientRepresentation();
|
||||
app.setClientId(entityId);
|
||||
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
app.setAttributes(attributes);
|
||||
|
||||
List<String> redirectUris = new LinkedList<>();
|
||||
app.setRedirectUris(redirectUris);
|
||||
|
||||
app.setFullScopeAllowed(true);
|
||||
app.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
|
||||
attributes.put(SamlProtocol.SAML_SERVER_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); // default to true
|
||||
attributes.put(SamlProtocol.SAML_SIGNATURE_ALGORITHM, SignatureAlgorithm.RSA_SHA256.toString());
|
||||
attributes.put(SamlProtocol.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||
SPSSODescriptorType spDescriptorType = CoreConfigUtil.getSPDescriptor(entity);
|
||||
if (spDescriptorType.isWantAssertionsSigned()) {
|
||||
attributes.put(SamlProtocol.SAML_ASSERTION_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||
}
|
||||
String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||
if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost);
|
||||
String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||
if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
|
||||
|
||||
String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||
if (assertionConsumerServicePostBinding != null) {
|
||||
attributes.put(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, assertionConsumerServicePostBinding);
|
||||
redirectUris.add(assertionConsumerServicePostBinding);
|
||||
}
|
||||
String assertionConsumerServiceRedirectBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||
if (assertionConsumerServiceRedirectBinding != null) {
|
||||
attributes.put(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE, assertionConsumerServiceRedirectBinding);
|
||||
redirectUris.add(assertionConsumerServiceRedirectBinding);
|
||||
}
|
||||
|
||||
for (KeyDescriptorType keyDescriptor : spDescriptorType.getKeyDescriptor()) {
|
||||
X509Certificate cert = null;
|
||||
try {
|
||||
cert = SAMLMetadataUtil.getCertificate(keyDescriptor);
|
||||
} catch (ConfigurationException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
String certPem = KeycloakModelUtils.getPemFromCertificate(cert);
|
||||
if (keyDescriptor.getUse() == KeyTypes.SIGNING) {
|
||||
attributes.put(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||
attributes.put(SamlProtocol.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, certPem);
|
||||
} else if (keyDescriptor.getUse() == KeyTypes.ENCRYPTION) {
|
||||
attributes.put(SamlProtocol.SAML_ENCRYPT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||
attributes.put(SamlProtocol.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, certPem);
|
||||
}
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static String getLogoutLocation(SPSSODescriptorType idp, String bindingURI) {
|
||||
String logoutResponseLocation = null;
|
||||
|
||||
List<EndpointType> endpoints = idp.getSingleLogoutService();
|
||||
for (EndpointType endpoint : endpoints) {
|
||||
if (endpoint.getBinding().toString().equals(bindingURI)) {
|
||||
if (endpoint.getLocation() != null) {
|
||||
logoutResponseLocation = endpoint.getLocation().toString();
|
||||
} else {
|
||||
logoutResponseLocation = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return logoutResponseLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientDescriptionConverter create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "saml2-entity-descriptor";
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.keycloak.exportimport.ClientImporter;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.resources.admin.RealmAuth;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class EntityDescriptorImporter implements ClientImporter {
|
||||
@Override
|
||||
public Object createJaxrsService(RealmModel realm, RealmAuth auth) {
|
||||
return new EntityDescriptorImporterService(realm, auth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.exportimport.ClientImporter;
|
||||
import org.keycloak.exportimport.ClientImporterFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class EntityDescriptorImporterFactory implements ClientImporterFactory {
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "SAML 2.0 Entity Descriptor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientImporter create(KeycloakSession session) {
|
||||
return new EntityDescriptorImporter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "saml2-entity-descriptor";
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
|
||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.saml.SignatureAlgorithm;
|
||||
import org.keycloak.services.resources.admin.RealmAuth;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
||||
import org.keycloak.saml.processing.core.saml.v2.util.SAMLMetadataUtil;
|
||||
import org.keycloak.saml.processing.core.util.CoreConfigUtil;
|
||||
import org.keycloak.dom.saml.v2.metadata.EndpointType;
|
||||
import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
|
||||
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
||||
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
|
||||
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
||||
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class EntityDescriptorImporterService {
|
||||
protected RealmModel realm;
|
||||
protected RealmAuth auth;
|
||||
|
||||
public EntityDescriptorImporterService(RealmModel realm, RealmAuth auth) {
|
||||
this.realm = realm;
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("upload")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
public void updateEntityDescriptor(@Context final UriInfo uriInfo, MultipartFormDataInput input) throws IOException {
|
||||
auth.requireManage();
|
||||
|
||||
Map<String, List<InputPart>> uploadForm = input.getFormDataMap();
|
||||
List<InputPart> inputParts = uploadForm.get("file");
|
||||
|
||||
InputStream is = inputParts.get(0).getBody(InputStream.class, null);
|
||||
|
||||
loadEntityDescriptors(is, realm);
|
||||
|
||||
}
|
||||
|
||||
public static void loadEntityDescriptors(InputStream is, RealmModel realm) {
|
||||
Object metadata = null;
|
||||
try {
|
||||
metadata = new SAMLParser().parse(is);
|
||||
} catch (ParsingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
EntitiesDescriptorType entities;
|
||||
|
||||
if (EntitiesDescriptorType.class.isInstance(metadata)) {
|
||||
entities = (EntitiesDescriptorType) metadata;
|
||||
} else {
|
||||
entities = new EntitiesDescriptorType();
|
||||
entities.addEntityDescriptor(metadata);
|
||||
}
|
||||
|
||||
for (Object o : entities.getEntityDescriptor()) {
|
||||
EntityDescriptorType entity = (EntityDescriptorType)o;
|
||||
String entityId = entity.getEntityID();
|
||||
ClientModel app = realm.addClient(entityId);
|
||||
app.setFullScopeAllowed(true);
|
||||
app.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
|
||||
app.setAttribute(SamlProtocol.SAML_SERVER_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); // default to true
|
||||
app.setAttribute(SamlProtocol.SAML_SIGNATURE_ALGORITHM, SignatureAlgorithm.RSA_SHA256.toString());
|
||||
app.setAttribute(SamlProtocol.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||
SPSSODescriptorType spDescriptorType = CoreConfigUtil.getSPDescriptor(entity);
|
||||
if (spDescriptorType.isWantAssertionsSigned()) {
|
||||
app.setAttribute(SamlProtocol.SAML_ASSERTION_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||
}
|
||||
String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||
if (logoutPost != null) app.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost);
|
||||
String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||
if (logoutPost != null) app.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
|
||||
|
||||
String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
|
||||
if (assertionConsumerServicePostBinding != null) {
|
||||
app.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, assertionConsumerServicePostBinding);
|
||||
app.addRedirectUri(assertionConsumerServicePostBinding);
|
||||
}
|
||||
String assertionConsumerServiceRedirectBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
|
||||
if (assertionConsumerServiceRedirectBinding != null) {
|
||||
app.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE, assertionConsumerServiceRedirectBinding);
|
||||
app.addRedirectUri(assertionConsumerServiceRedirectBinding);
|
||||
}
|
||||
|
||||
for (KeyDescriptorType keyDescriptor : spDescriptorType.getKeyDescriptor()) {
|
||||
X509Certificate cert = null;
|
||||
try {
|
||||
cert = SAMLMetadataUtil.getCertificate(keyDescriptor);
|
||||
} catch (ConfigurationException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
String certPem = KeycloakModelUtils.getPemFromCertificate(cert);
|
||||
if (keyDescriptor.getUse() == KeyTypes.SIGNING) {
|
||||
app.setAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||
app.setAttribute(SamlProtocol.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, certPem);
|
||||
} else if (keyDescriptor.getUse() == KeyTypes.ENCRYPTION) {
|
||||
app.setAttribute(SamlProtocol.SAML_ENCRYPT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
|
||||
app.setAttribute(SamlProtocol.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, certPem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getLogoutLocation(SPSSODescriptorType idp, String bindingURI) {
|
||||
String logoutResponseLocation = null;
|
||||
|
||||
List<EndpointType> endpoints = idp.getSingleLogoutService();
|
||||
for (EndpointType endpoint : endpoints) {
|
||||
if (endpoint.getBinding().toString().equals(bindingURI)) {
|
||||
if (endpoint.getLocation() != null) {
|
||||
logoutResponseLocation = endpoint.getLocation().toString();
|
||||
} else {
|
||||
logoutResponseLocation = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return logoutResponseLocation;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -485,7 +485,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
}
|
||||
if (logoutServiceUrl == null && client instanceof ClientModel) logoutServiceUrl = ((ClientModel)client).getManagementUrl();
|
||||
if (logoutServiceUrl == null || logoutServiceUrl.trim().equals("")) return null;
|
||||
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), logoutServiceUrl);
|
||||
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), client.getRootUrl(), logoutServiceUrl);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter
|
|
@ -1 +0,0 @@
|
|||
org.keycloak.protocol.saml.EntityDescriptorImporterFactory
|
|
@ -1,8 +1,7 @@
|
|||
package org.keycloak.exportimport;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.services.resources.admin.RealmAuth;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
||||
/**
|
||||
* Provider plugin interface for importing clients from an arbitrary configuration format
|
||||
|
@ -10,6 +9,8 @@ import org.keycloak.services.resources.admin.RealmAuth;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ClientImporter extends Provider {
|
||||
public Object createJaxrsService(RealmModel realm, RealmAuth auth);
|
||||
public interface ClientDescriptionConverter extends Provider {
|
||||
|
||||
ClientRepresentation convertToInternal(String description);
|
||||
|
||||
}
|
|
@ -8,6 +8,8 @@ import org.keycloak.provider.ProviderFactory;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ClientImporterFactory extends ProviderFactory<ClientImporter> {
|
||||
public String getDisplayName();
|
||||
public interface ClientDescriptionConverterFactory extends ProviderFactory<ClientDescriptionConverter> {
|
||||
|
||||
boolean isSupported(String description);
|
||||
|
||||
}
|
|
@ -7,7 +7,7 @@ import org.keycloak.provider.Spi;
|
|||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientImportSpi implements Spi {
|
||||
public class ClientDescriptionConverterSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
|
@ -16,16 +16,17 @@ public class ClientImportSpi implements Spi {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "client-import";
|
||||
return "client-description-converter";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return ClientImporter.class;
|
||||
return ClientDescriptionConverter.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return ClientImporterFactory.class;
|
||||
return ClientDescriptionConverterFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.keycloak.exportimport;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class KeycloakClientDescriptionConverter implements ClientDescriptionConverterFactory, ClientDescriptionConverter {
|
||||
|
||||
public static final String ID = "keycloak";
|
||||
|
||||
@Override
|
||||
public boolean isSupported(String description) {
|
||||
description = description.trim();
|
||||
return (description.startsWith("{") && description.endsWith("}") && description.contains("\"clientId\""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRepresentation convertToInternal(String description) {
|
||||
try {
|
||||
return JsonSerialization.readValue(description, ClientRepresentation.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientDescriptionConverter create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package org.keycloak.protocol.oidc;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
|
||||
|
||||
@Override
|
||||
public boolean isSupported(String description) {
|
||||
description = description.trim();
|
||||
return (description.startsWith("{") && description.endsWith("}") && description.contains("\"redirect_uris\""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRepresentation convertToInternal(String description) {
|
||||
try {
|
||||
OIDCClientRepresentation oidcRep = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
|
||||
|
||||
ClientRepresentation client = new ClientRepresentation();
|
||||
client.setClientId(KeycloakModelUtils.generateId());
|
||||
client.setName(oidcRep.getClientName());
|
||||
client.setRedirectUris(oidcRep.getRedirectUris());
|
||||
client.setBaseUrl(oidcRep.getClientUri());
|
||||
|
||||
return client;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientDescriptionConverter create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "openid-connect";
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
|||
import org.keycloak.protocol.ProtocolMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
|
||||
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
@ -217,7 +218,7 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
AccessToken token = initToken(realm, client, user, userSession, clientSession);
|
||||
AccessToken token = initToken(realm, client, user, userSession, clientSession, session.getContext().getUri());
|
||||
for (RoleModel role : requestedRoles) {
|
||||
addComposites(token, role);
|
||||
}
|
||||
|
@ -380,7 +381,7 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
|
||||
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, ClientSessionModel clientSession) {
|
||||
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, ClientSessionModel clientSession, UriInfo uriInfo) {
|
||||
AccessToken token = new AccessToken();
|
||||
if (clientSession != null) token.clientSession(clientSession.getId());
|
||||
token.id(KeycloakModelUtils.generateId());
|
||||
|
@ -398,7 +399,7 @@ public class TokenManager {
|
|||
}
|
||||
Set<String> allowedOrigins = client.getWebOrigins();
|
||||
if (allowedOrigins != null) {
|
||||
token.setAllowedOrigins(allowedOrigins);
|
||||
token.setAllowedOrigins(WebOriginsUtils.resolveValidWebOrigins(uriInfo, client));
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ public class LoginStatusIframeEndpoint {
|
|||
}
|
||||
}
|
||||
|
||||
for (String r : RedirectUtils.resolveValidRedirects(uriInfo, client.getRedirectUris())) {
|
||||
for (String r : RedirectUtils.resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris())) {
|
||||
int i = r.indexOf('/', 8);
|
||||
if (i != -1) {
|
||||
r = r.substring(0, i);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package org.keycloak.protocol.oidc.representations;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class OIDCClientRepresentation {
|
||||
|
||||
@JsonProperty("redirect_uris")
|
||||
private List<String> redirectUris;
|
||||
|
||||
@JsonProperty("client_name")
|
||||
private String clientName;
|
||||
|
||||
@JsonProperty("client_uri")
|
||||
private String clientUri;
|
||||
|
||||
public List<String> getRedirectUris() {
|
||||
return redirectUris;
|
||||
}
|
||||
|
||||
public void setRedirectUris(List<String> redirectUris) {
|
||||
this.redirectUris = redirectUris;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return clientName;
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public String getClientUri() {
|
||||
return clientUri;
|
||||
}
|
||||
|
||||
public void setClientUri(String clientUri) {
|
||||
this.clientUri = clientUri;
|
||||
}
|
||||
|
||||
}
|
|
@ -19,22 +19,22 @@ public class RedirectUtils {
|
|||
private static final Logger logger = Logger.getLogger(RedirectUtils.class);
|
||||
|
||||
public static String verifyRealmRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm) {
|
||||
Set<String> validRedirects = getValidateRedirectUris(realm);
|
||||
return verifyRedirectUri(uriInfo, redirectUri, realm, validRedirects);
|
||||
Set<String> validRedirects = getValidateRedirectUris(uriInfo, realm);
|
||||
return verifyRedirectUri(uriInfo, null, redirectUri, realm, validRedirects);
|
||||
}
|
||||
|
||||
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
|
||||
Set<String> validRedirects = client.getRedirectUris();
|
||||
return verifyRedirectUri(uriInfo, redirectUri, realm, validRedirects);
|
||||
return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, validRedirects);
|
||||
}
|
||||
|
||||
public static Set<String> resolveValidRedirects(UriInfo uriInfo, Set<String> validRedirects) {
|
||||
public static Set<String> resolveValidRedirects(UriInfo uriInfo, String rootUrl, Set<String> validRedirects) {
|
||||
// If the valid redirect URI is relative (no scheme, host, port) then use the request's scheme, host, and port
|
||||
Set<String> resolveValidRedirects = new HashSet<String>();
|
||||
for (String validRedirect : validRedirects) {
|
||||
resolveValidRedirects.add(validRedirect); // add even relative urls.
|
||||
if (validRedirect.startsWith("/")) {
|
||||
validRedirect = relativeToAbsoluteURI(uriInfo, validRedirect);
|
||||
validRedirect = relativeToAbsoluteURI(uriInfo, rootUrl, validRedirect);
|
||||
logger.debugv("replacing relative valid redirect with: {0}", validRedirect);
|
||||
resolveValidRedirects.add(validRedirect);
|
||||
}
|
||||
|
@ -42,17 +42,15 @@ public class RedirectUtils {
|
|||
return resolveValidRedirects;
|
||||
}
|
||||
|
||||
private static Set<String> getValidateRedirectUris(RealmModel realm) {
|
||||
Set<String> redirects = new HashSet<String>();
|
||||
private static Set<String> getValidateRedirectUris(UriInfo uriInfo, RealmModel realm) {
|
||||
Set<String> redirects = new HashSet<>();
|
||||
for (ClientModel client : realm.getClients()) {
|
||||
for (String redirect : client.getRedirectUris()) {
|
||||
redirects.add(redirect);
|
||||
}
|
||||
redirects.addAll(resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris()));
|
||||
}
|
||||
return redirects;
|
||||
}
|
||||
|
||||
private static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, Set<String> validRedirects) {
|
||||
private static String verifyRedirectUri(UriInfo uriInfo, String rootUrl, String redirectUri, RealmModel realm, Set<String> validRedirects) {
|
||||
if (redirectUri == null) {
|
||||
if (validRedirects.size() != 1) return null;
|
||||
String validRedirect = validRedirects.iterator().next();
|
||||
|
@ -66,7 +64,7 @@ public class RedirectUtils {
|
|||
redirectUri = null;
|
||||
} else {
|
||||
String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
|
||||
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
|
||||
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, rootUrl, validRedirects);
|
||||
|
||||
boolean valid = matchesRedirects(resolveValidRedirects, r);
|
||||
|
||||
|
@ -86,7 +84,7 @@ public class RedirectUtils {
|
|||
valid = matchesRedirects(resolveValidRedirects, r);
|
||||
}
|
||||
if (valid && redirectUri.startsWith("/")) {
|
||||
redirectUri = relativeToAbsoluteURI(uriInfo, redirectUri);
|
||||
redirectUri = relativeToAbsoluteURI(uriInfo, rootUrl, redirectUri);
|
||||
}
|
||||
redirectUri = valid ? redirectUri : null;
|
||||
}
|
||||
|
@ -98,13 +96,16 @@ public class RedirectUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static String relativeToAbsoluteURI(UriInfo uriInfo, String relative) {
|
||||
URI baseUri = uriInfo.getBaseUri();
|
||||
String uri = baseUri.getScheme() + "://" + baseUri.getHost();
|
||||
if (baseUri.getPort() != -1) {
|
||||
uri += ":" + baseUri.getPort();
|
||||
private static String relativeToAbsoluteURI(UriInfo uriInfo, String rootUrl, String relative) {
|
||||
if (rootUrl == null) {
|
||||
URI baseUri = uriInfo.getBaseUri();
|
||||
String uri = baseUri.getScheme() + "://" + baseUri.getHost();
|
||||
if (baseUri.getPort() != -1) {
|
||||
uri += ":" + baseUri.getPort();
|
||||
}
|
||||
rootUrl = uri;
|
||||
}
|
||||
relative = uri + relative;
|
||||
relative = rootUrl + relative;
|
||||
return relative;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package org.keycloak.protocol.oidc.utils;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.util.UriUtils;
|
||||
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by st on 22.09.15.
|
||||
*/
|
||||
public class WebOriginsUtils {
|
||||
|
||||
public static final String INCLUDE_REDIRECTS = "+";
|
||||
|
||||
public static Set<String> resolveValidWebOrigins(UriInfo uriInfo, ClientModel client) {
|
||||
Set<String> webOrigins = client.getWebOrigins();
|
||||
if (webOrigins != null && webOrigins.contains("+")) {
|
||||
webOrigins.remove(INCLUDE_REDIRECTS);
|
||||
client.getRedirectUris();
|
||||
for (String redirectUri : RedirectUtils.resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris())) {
|
||||
if (redirectUri.startsWith("http://") || redirectUri.startsWith("https://")) {
|
||||
webOrigins.add(UriUtils.getOrigin(redirectUri));
|
||||
}
|
||||
}
|
||||
}
|
||||
return webOrigins;
|
||||
}
|
||||
|
||||
}
|
|
@ -46,8 +46,8 @@ public class ResourceAdminManager {
|
|||
this.session = session;
|
||||
}
|
||||
|
||||
public static String resolveUri(URI requestUri, String uri) {
|
||||
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, uri);
|
||||
public static String resolveUri(URI requestUri, String rootUrl, String uri) {
|
||||
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, rootUrl, uri);
|
||||
return StringPropertyReplacer.replaceProperties(absoluteURI);
|
||||
|
||||
}
|
||||
|
@ -58,8 +58,7 @@ public class ResourceAdminManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
// this is to support relative admin urls when keycloak and clients are deployed on the same machine
|
||||
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, mgmtUrl);
|
||||
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, client.getRootUrl(), mgmtUrl);
|
||||
|
||||
// this is for resolving URI like "http://${jboss.host.name}:8080/..." in order to send request to same machine and avoid request to LB in cluster environment
|
||||
return StringPropertyReplacer.replaceProperties(absoluteURI);
|
||||
|
|
|
@ -779,7 +779,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
if (referrerUri != null) {
|
||||
referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, referrerClient);
|
||||
} else {
|
||||
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), referrerClient.getBaseUrl());
|
||||
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), client.getRootUrl(), referrerClient.getBaseUrl());
|
||||
}
|
||||
|
||||
if (referrerUri != null) {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package org.keycloak.services.resources;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.BadRequestException;
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||
import org.keycloak.exportimport.KeycloakClientDescriptionConverter;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientRegistrationService {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(ClientRegistrationService.class);
|
||||
|
||||
private RealmModel realm;
|
||||
|
||||
private EventBuilder event;
|
||||
|
||||
@Context
|
||||
private KeycloakSession session;
|
||||
|
||||
public ClientRegistrationService(RealmModel realm, EventBuilder event) {
|
||||
this.realm = realm;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
|
||||
public Response create(String description, @QueryParam("format") String format) {
|
||||
if (format == null) {
|
||||
format = KeycloakClientDescriptionConverter.ID;
|
||||
}
|
||||
|
||||
ClientDescriptionConverter converter = session.getProvider(ClientDescriptionConverter.class, format);
|
||||
if (converter == null) {
|
||||
throw new BadRequestException("Invalid format");
|
||||
}
|
||||
ClientRepresentation rep = converter.convertToInternal(description);
|
||||
|
||||
try {
|
||||
ClientModel clientModel = RepresentationToModel.createClient(session, realm, rep, true);
|
||||
rep = ModelToRepresentation.toRepresentation(clientModel);
|
||||
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
|
||||
return Response.created(uri).entity(rep).build();
|
||||
} catch (ModelDuplicateException e) {
|
||||
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{clientId}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ClientRepresentation get(@PathParam("clientId") String clientId) {
|
||||
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
|
||||
ClientModel client = clientAuth.getClient();
|
||||
if (client == null) {
|
||||
throw new NotFoundException("Client not found");
|
||||
}
|
||||
return ModelToRepresentation.toRepresentation(client);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{clientId}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
if (client == null) {
|
||||
throw new NotFoundException("Client not found");
|
||||
}
|
||||
RepresentationToModel.updateClient(rep, client);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{clientId}")
|
||||
public void delete(@PathParam("clientId") String clientId) {
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
if (client == null) {
|
||||
throw new NotFoundException("Client not found");
|
||||
}
|
||||
realm.removeClient(client.getId());
|
||||
}
|
||||
|
||||
}
|
|
@ -112,6 +112,15 @@ public class RealmsResource {
|
|||
return service;
|
||||
}
|
||||
|
||||
// @Path("{realm}/client-registration")
|
||||
// public ClientRegistrationService getClientsService(final @PathParam("realm") String name) {
|
||||
// RealmModel realm = init(name);
|
||||
// EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||
// ClientRegistrationService service = new ClientRegistrationService(realm, event);
|
||||
// ResteasyProviderFactory.getInstance().injectProperties(service);
|
||||
// return service;
|
||||
// }
|
||||
|
||||
@Path("{realm}/clients-managements")
|
||||
public ClientsManagementService getClientsManagementService(final @PathParam("realm") String name) {
|
||||
RealmModel realm = init(name);
|
||||
|
|
|
@ -13,7 +13,8 @@ import org.keycloak.events.EventType;
|
|||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.events.admin.AdminEventQuery;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.exportimport.ClientImporter;
|
||||
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
|
@ -25,7 +26,9 @@ import org.keycloak.models.cache.CacheUserProvider;
|
|||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
@ -99,10 +102,18 @@ public class RealmAdminResource {
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("client-importers/{formatId}")
|
||||
public Object getClientImporter(@PathParam("formatId") String formatId) {
|
||||
ClientImporter importer = session.getProvider(ClientImporter.class, formatId);
|
||||
return importer.createJaxrsService(realm, auth);
|
||||
@Path("client-description-converter")
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ClientRepresentation convertClientDescription(String description) {
|
||||
for (ProviderFactory<ClientDescriptionConverter> factory : session.getKeycloakSessionFactory().getProviderFactories(ClientDescriptionConverter.class)) {
|
||||
if (((ClientDescriptionConverterFactory) factory).isSupported(description)) {
|
||||
return factory.create(session).convertToInternal(description);
|
||||
}
|
||||
}
|
||||
|
||||
throw new BadRequestException("Unsupported format");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,8 +4,6 @@ import org.keycloak.broker.provider.IdentityProvider;
|
|||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.exportimport.ClientImporter;
|
||||
import org.keycloak.exportimport.ClientImporterFactory;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -51,7 +49,6 @@ public class ServerInfoAdminResource {
|
|||
setSocialProviders(info);
|
||||
setIdentityProviders(info);
|
||||
setThemes(info);
|
||||
setClientImporters(info);
|
||||
setProviders(info);
|
||||
setProtocolMapperTypes(info);
|
||||
setBuiltinProtocolMappers(info);
|
||||
|
@ -144,7 +141,7 @@ public class ServerInfoAdminResource {
|
|||
ProtocolMapper mapper = (ProtocolMapper)p;
|
||||
List<ProtocolMapperTypeRepresentation> types = info.getProtocolMapperTypes().get(mapper.getProtocol());
|
||||
if (types == null) {
|
||||
types = new LinkedList<ProtocolMapperTypeRepresentation>();
|
||||
types = new LinkedList<>();
|
||||
info.getProtocolMapperTypes().put(mapper.getProtocol(), types);
|
||||
}
|
||||
ProtocolMapperTypeRepresentation rep = new ProtocolMapperTypeRepresentation();
|
||||
|
@ -179,17 +176,6 @@ public class ServerInfoAdminResource {
|
|||
}
|
||||
}
|
||||
|
||||
private void setClientImporters(ServerInfoRepresentation info) {
|
||||
info.setClientImporters(new LinkedList<Map<String, String>>());
|
||||
for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(ClientImporter.class)) {
|
||||
ClientImporterFactory factory = (ClientImporterFactory)p;
|
||||
Map<String, String> data = new HashMap<String, String>();
|
||||
data.put("id", factory.getId());
|
||||
data.put("name", factory.getDisplayName());
|
||||
info.getClientImporters().add(data);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, List<String>> createEnumsMap(Class... enums) {
|
||||
Map<String, List<String>> m = new HashMap<>();
|
||||
for (Class e : enums) {
|
||||
|
|
|
@ -8,13 +8,17 @@ import java.net.URI;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ResolveRelative {
|
||||
public static String resolveRelativeUri(URI requestUri, String url) {
|
||||
public static String resolveRelativeUri(URI requestUri, String rootUrl, String url) {
|
||||
if (url == null || !url.startsWith("/")) return url;
|
||||
UriBuilder builder = UriBuilder.fromPath(url).host(requestUri.getHost());
|
||||
builder.scheme(requestUri.getScheme());
|
||||
if (requestUri.getPort() != -1) {
|
||||
builder.port(requestUri.getPort());
|
||||
if (rootUrl != null) {
|
||||
return rootUrl + url;
|
||||
} else {
|
||||
UriBuilder builder = UriBuilder.fromPath(url).host(requestUri.getHost());
|
||||
builder.scheme(requestUri.getScheme());
|
||||
if (requestUri.getPort() != -1) {
|
||||
builder.port(requestUri.getPort());
|
||||
}
|
||||
return builder.build().toString();
|
||||
}
|
||||
return builder.build().toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
org.keycloak.exportimport.KeycloakClientDescriptionConverter
|
||||
org.keycloak.protocol.oidc.OIDCClientDescriptionConverter
|
|
@ -1,6 +1,6 @@
|
|||
org.keycloak.protocol.LoginProtocolSpi
|
||||
org.keycloak.protocol.ProtocolMapperSpi
|
||||
org.keycloak.exportimport.ClientImportSpi
|
||||
org.keycloak.exportimport.ClientDescriptionConverterSpi
|
||||
org.keycloak.wellknown.WellKnownSpi
|
||||
org.keycloak.messages.MessagesSpi
|
||||
org.keycloak.authentication.AuthenticatorSpi
|
||||
|
|
|
@ -177,6 +177,7 @@ public class AdminAPITest {
|
|||
if (appRep.isBearerOnly() != null) Assert.assertEquals(appRep.isBearerOnly(), storedApp.isBearerOnly());
|
||||
if (appRep.isPublicClient() != null) Assert.assertEquals(appRep.isPublicClient(), storedApp.isPublicClient());
|
||||
if (appRep.isFullScopeAllowed() != null) Assert.assertEquals(appRep.isFullScopeAllowed(), storedApp.isFullScopeAllowed());
|
||||
if (appRep.getRootUrl() != null) Assert.assertEquals(appRep.getRootUrl(), storedApp.getRootUrl());
|
||||
if (appRep.getAdminUrl() != null) Assert.assertEquals(appRep.getAdminUrl(), storedApp.getAdminUrl());
|
||||
if (appRep.getBaseUrl() != null) Assert.assertEquals(appRep.getBaseUrl(), storedApp.getBaseUrl());
|
||||
if (appRep.isSurrogateAuthRequired() != null) Assert.assertEquals(appRep.isSurrogateAuthRequired(), storedApp.isSurrogateAuthRequired());
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
package org.keycloak.testsuite.admin;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -132,4 +137,35 @@ public class RealmTest extends AbstractClientTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertKeycloakClientDescription() throws IOException {
|
||||
ClientRepresentation description = new ClientRepresentation();
|
||||
description.setClientId("client-id");
|
||||
description.setRedirectUris(Collections.singletonList("http://localhost"));
|
||||
|
||||
ClientRepresentation converted = realm.convertClientDescription(JsonSerialization.writeValueAsString(description));
|
||||
assertEquals("client-id", converted.getClientId());
|
||||
assertEquals("http://localhost", converted.getRedirectUris().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertOIDCClientDescription() throws IOException {
|
||||
String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/client-oidc.json"));
|
||||
|
||||
ClientRepresentation converted = realm.convertClientDescription(description);
|
||||
assertEquals(36, converted.getClientId().length());
|
||||
assertEquals(1, converted.getRedirectUris().size());
|
||||
assertEquals("http://localhost", converted.getRedirectUris().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertSAMLClientDescription() throws IOException {
|
||||
String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/saml-entity-descriptor.xml"));
|
||||
|
||||
ClientRepresentation converted = realm.convertClientDescription(description);
|
||||
assertEquals("loadbalancer-9.siroe.com", converted.getClientId());
|
||||
assertEquals(1, converted.getRedirectUris().size());
|
||||
assertEquals("https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp", converted.getRedirectUris().get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public class SamlAdapterTest {
|
|||
initializeSamlSecuredWar("/keycloak-saml/bad-client-signed-post", "/bad-client-sales-post-sig", "bad-client-post-sig.war", classLoader);
|
||||
initializeSamlSecuredWar("/keycloak-saml/bad-realm-signed-post", "/bad-realm-sales-post-sig", "bad-realm-post-sig.war", classLoader);
|
||||
initializeSamlSecuredWar("/keycloak-saml/encrypted-post", "/sales-post-enc", "post-enc.war", classLoader);
|
||||
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth", this);
|
||||
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth");
|
||||
server.getServer().deploy(createDeploymentInfo("employee.war", "/employee", SamlSPFacade.class));
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.testsuite.keycloaksaml;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
|
@ -8,6 +9,8 @@ import org.junit.Test;
|
|||
import org.junit.rules.ExternalResource;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.adapters.saml.SamlPrincipal;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
|
@ -24,6 +27,7 @@ import org.keycloak.protocol.saml.mappers.HardcodedRole;
|
|||
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
||||
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
@ -34,6 +38,7 @@ import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
|||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
|
@ -51,6 +56,8 @@ import java.io.InputStream;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -106,9 +113,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
|
||||
public void testPostSimpleLoginLogout() {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
|
||||
|
@ -117,9 +124,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
|
||||
public void testPostSimpleUnauthorized(CheckAuthError error) {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("unauthorized", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||
System.out.println(driver.getPageSource());
|
||||
error.check(driver);
|
||||
}
|
||||
|
@ -127,7 +134,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
public void testPostSimpleLoginLogoutIdpInitiated() {
|
||||
driver.navigate().to(AUTH_SERVER_URL + "/realms/demo/protocol/saml/clients/sales-post");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
|
||||
|
@ -136,9 +143,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
|
||||
public void testPostSignedLoginLogout() {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig?GLO=true");
|
||||
checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||
|
@ -146,9 +153,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
}
|
||||
public void testPostSignedLoginLogoutTransientNameID() {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertFalse(driver.getPageSource().contains("bburke"));
|
||||
Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
|
||||
|
@ -158,9 +165,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
}
|
||||
public void testPostSignedLoginLogoutPersistentNameID() {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertFalse(driver.getPageSource().contains("bburke"));
|
||||
Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
|
||||
|
@ -170,9 +177,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
}
|
||||
public void testPostSignedLoginLogoutEmailNameID() {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-email/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-email/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-email/");
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertTrue(driver.getPageSource().contains("principal=bburke@redhat.com"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-email?GLO=true");
|
||||
|
@ -188,8 +195,8 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||
System.out.println(driver.getCurrentUrl());
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee/");
|
||||
Assert.assertEquals(SamlSPFacade.sentRelayState, SamlSPFacade.RELAY_STATE);
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee/");
|
||||
assertEquals(SamlSPFacade.sentRelayState, SamlSPFacade.RELAY_STATE);
|
||||
Assert.assertNotNull(SamlSPFacade.samlResponse);
|
||||
|
||||
}
|
||||
|
@ -206,13 +213,13 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
requiredRoles.add("user");
|
||||
SendUsernameServlet.checkRoles = requiredRoles;
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
||||
SendUsernameServlet.checkRoles = null;
|
||||
SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
|
||||
Assert.assertNotNull(principal);
|
||||
Assert.assertEquals("bburke@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
|
||||
Assert.assertEquals("bburke@redhat.com", principal.getFriendlyAttribute("email"));
|
||||
Assert.assertEquals("617", principal.getAttribute("phone"));
|
||||
assertEquals("bburke@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
|
||||
assertEquals("bburke@redhat.com", principal.getFriendlyAttribute("email"));
|
||||
assertEquals("617", principal.getAttribute("phone"));
|
||||
Assert.assertNull(principal.getFriendlyAttribute("phone"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/?GLO=true");
|
||||
checkLoggedOut(APP_SERVER_BASE_URL + "/employee2/");
|
||||
|
@ -252,11 +259,11 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
requiredRoles.add("pee-on");
|
||||
SendUsernameServlet.checkRoles = requiredRoles;
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
||||
SendUsernameServlet.checkRoles = null;
|
||||
SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
|
||||
Assert.assertNotNull(principal);
|
||||
Assert.assertEquals("hard", principal.getAttribute("hardcoded-attribute"));
|
||||
assertEquals("hard", principal.getAttribute("hardcoded-attribute"));
|
||||
|
||||
|
||||
}
|
||||
|
@ -266,7 +273,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig/");
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig?GLO=true");
|
||||
checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig/");
|
||||
|
@ -277,7 +284,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front?GLO=true");
|
||||
checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||
|
@ -291,19 +298,19 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||
System.out.println("login to form");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
|
||||
// visit 2nd app
|
||||
System.out.println("visit 2nd app ");
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
|
||||
// visit 3rd app
|
||||
System.out.println("visit 3rd app ");
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
|
||||
// logout of first app
|
||||
|
@ -320,9 +327,9 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
|
||||
public void testPostEncryptedLoginLogout() {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-enc/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-enc/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-enc/");
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-enc?GLO=true");
|
||||
checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-enc/");
|
||||
|
@ -330,8 +337,8 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
}
|
||||
public void testPostBadClientSignature() {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/bad-client-sales-post-sig/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
Assert.assertEquals(driver.getTitle(), "We're sorry...");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getTitle(), "We're sorry...");
|
||||
|
||||
}
|
||||
public static interface CheckAuthError {
|
||||
|
@ -340,39 +347,19 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
|
||||
public void testPostBadRealmSignature(CheckAuthError error) {
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
|
||||
System.out.println(driver.getPageSource());
|
||||
error.check(driver);
|
||||
}
|
||||
|
||||
private static String createToken(String AUTH_SERVER_URL, AbstractKeycloakRule keycloakRule) {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmManager manager = new RealmManager(session);
|
||||
|
||||
RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
|
||||
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
||||
TokenManager tm = new TokenManager();
|
||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
||||
clientSession.setNote(OIDCLoginProtocol.ISSUER, AUTH_SERVER_URL + "/realms/master");
|
||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
|
||||
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, true, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
|
||||
return tm.encodeToken(adminRealm, token);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testMetadataPostSignedLoginLogout() throws Exception {
|
||||
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-metadata/");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-metadata/");
|
||||
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-metadata/");
|
||||
String pageSource = driver.getPageSource();
|
||||
Assert.assertTrue(pageSource.contains("bburke"));
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-metadata?GLO=true");
|
||||
|
@ -380,30 +367,21 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
|
||||
}
|
||||
|
||||
public static void uploadSP(String AUTH_SERVER_URL, AbstractKeycloakRule keycloakRule) {
|
||||
String token = createToken(AUTH_SERVER_URL, keycloakRule);
|
||||
final String authHeader = "Bearer " + token;
|
||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
|
||||
}
|
||||
};
|
||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
||||
UriBuilder authBase = UriBuilder.fromUri(AUTH_SERVER_URL + "");
|
||||
WebTarget adminRealms = client.target(AdminRoot.realmsUrl(authBase));
|
||||
public static void uploadSP(String AUTH_SERVER_URL) {
|
||||
try {
|
||||
Keycloak keycloak = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID, null);
|
||||
RealmResource admin = keycloak.realm("demo");
|
||||
|
||||
admin.toRepresentation();
|
||||
|
||||
MultipartFormDataOutput formData = new MultipartFormDataOutput();
|
||||
InputStream is = SamlAdapterTestStrategy.class.getResourceAsStream("/keycloak-saml/sp-metadata.xml");
|
||||
Assert.assertNotNull(is);
|
||||
formData.addFormData("file", is, MediaType.APPLICATION_XML_TYPE);
|
||||
ClientRepresentation clientRep = admin.convertClientDescription(IOUtils.toString(SamlAdapterTestStrategy.class.getResourceAsStream("/keycloak-saml/sp-metadata.xml")));
|
||||
Response response = admin.clients().create(clientRep);
|
||||
|
||||
WebTarget upload = adminRealms.path("demo/client-importers/saml2-entity-descriptor/upload");
|
||||
System.out.println(upload.getUri());
|
||||
Response response = upload.request().post(Entity.entity(formData, MediaType.MULTIPART_FORM_DATA));
|
||||
Assert.assertEquals(204, response.getStatus());
|
||||
response.close();
|
||||
client.close();
|
||||
assertEquals(201, response.getStatus());
|
||||
|
||||
keycloak.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package org.keycloak.testsuite.saml;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
|
@ -22,9 +25,11 @@ import org.keycloak.protocol.saml.mappers.HardcodedRole;
|
|||
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
||||
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.admin.AdminRoot;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
|
@ -56,6 +61,8 @@ import java.io.ByteArrayInputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -480,30 +487,21 @@ public class SamlBindingTest {
|
|||
}
|
||||
|
||||
public static void uploadSP() {
|
||||
String token = createToken();
|
||||
final String authHeader = "Bearer " + token;
|
||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
|
||||
}
|
||||
};
|
||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
||||
UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
|
||||
WebTarget adminRealms = client.target(AdminRoot.realmsUrl(authBase));
|
||||
try {
|
||||
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID, null);
|
||||
RealmResource admin = keycloak.realm("demo");
|
||||
|
||||
admin.toRepresentation();
|
||||
|
||||
MultipartFormDataOutput formData = new MultipartFormDataOutput();
|
||||
InputStream is = SamlBindingTest.class.getResourceAsStream("/saml/sp-metadata.xml");
|
||||
Assert.assertNotNull(is);
|
||||
formData.addFormData("file", is, MediaType.APPLICATION_XML_TYPE);
|
||||
ClientRepresentation clientRep = admin.convertClientDescription(IOUtils.toString(SamlBindingTest.class.getResourceAsStream("/saml/sp-metadata.xml")));
|
||||
Response response = admin.clients().create(clientRep);
|
||||
|
||||
WebTarget upload = adminRealms.path("demo/client-importers/saml2-entity-descriptor/upload");
|
||||
System.out.println(upload.getUri());
|
||||
Response response = upload.request().post(Entity.entity(formData, MediaType.MULTIPART_FORM_DATA));
|
||||
Assert.assertEquals(204, response.getStatus());
|
||||
response.close();
|
||||
client.close();
|
||||
assertEquals(201, response.getStatus());
|
||||
|
||||
keycloak.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"client_name": "Name",
|
||||
"redirect_uris": [
|
||||
"http://localhost"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<EntityDescriptor
|
||||
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||
entityID="loadbalancer-9.siroe.com">
|
||||
<SPSSODescriptor
|
||||
AuthnRequestsSigned="false"
|
||||
WantAssertionsSigned="false"
|
||||
protocolSupportEnumeration=
|
||||
"urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
<KeyDescriptor use="signing">
|
||||
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||
<X509Data>
|
||||
<X509Certificate>
|
||||
MIICYDCCAgqgAwIBAgICBoowDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||
EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
|
||||
dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
|
||||
dGUgTWFuYWdlcjAeFw0wNjExMDIxOTExMzRaFw0xMDA3MjkxOTExMzRaMDcxEjAQBgNVBAoTCXNp
|
||||
cm9lLmNvbTEhMB8GA1UEAxMYbG9hZGJhbGFuY2VyLTkuc2lyb2UuY29tMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQCjOwa5qoaUuVnknqf5pdgAJSEoWlvx/jnUYbkSDpXLzraEiy2UhvwpoBgB
|
||||
EeTSUaPPBvboCItchakPI6Z/aFdH3Wmjuij9XD8r1C+q//7sUO0IGn0ORycddHhoo0aSdnnxGf9V
|
||||
tREaqKm9dJ7Yn7kQHjo2eryMgYxtr/Z5Il5F+wIDAQABo2AwXjARBglghkgBhvhCAQEEBAMCBkAw
|
||||
DgYDVR0PAQH/BAQDAgTwMB8GA1UdIwQYMBaAFDugITflTCfsWyNLTXDl7cMDUKuuMBgGA1UdEQQR
|
||||
MA+BDW1hbGxhQHN1bi5jb20wDQYJKoZIhvcNAQEEBQADQQB/6DOB6sRqCZu2OenM9eQR0gube85e
|
||||
nTTxU4a7x1naFxzYXK1iQ1vMARKMjDb19QEJIEJKZlDK4uS7yMlf1nFS
|
||||
</X509Certificate>
|
||||
</X509Data>
|
||||
</KeyInfo>
|
||||
</KeyDescriptor>
|
||||
<KeyDescriptor use="encryption">
|
||||
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||
<X509Data>
|
||||
<X509Certificate>
|
||||
MIICTDCCAfagAwIBAgICBo8wDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||
EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
|
||||
dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
|
||||
dGUgTWFuYWdlcjAeFw0wNjExMDcyMzU2MTdaFw0xMDA4MDMyMzU2MTdaMCMxITAfBgNVBAMTGGxv
|
||||
YWRiYWxhbmNlci05LnNpcm9lLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw574iRU6
|
||||
HsSO4LXW/OGTXyfsbGv6XRVOoy3v+J1pZ51KKejcDjDJXNkKGn3/356AwIaqbcymWd59T0zSqYfR
|
||||
Hn+45uyjYxRBmVJseLpVnOXLub9jsjULfGx0yjH4w+KsZSZCXatoCHbj/RJtkzuZY6V9to/hkH3S
|
||||
InQB4a3UAgMCAwEAAaNgMF4wEQYJYIZIAYb4QgEBBAQDAgZAMA4GA1UdDwEB/wQEAwIE8DAfBgNV
|
||||
HSMEGDAWgBQ7oCE35Uwn7FsjS01w5e3DA1CrrjAYBgNVHREEETAPgQ1tYWxsYUBzdW4uY29tMA0G
|
||||
CSqGSIb3DQEBBAUAA0EAMlbfBg/ff0Xkv4DOR5LEqmfTZKqgdlD81cXynfzlF7XfnOqI6hPIA90I
|
||||
x5Ql0ejivIJAYcMGUyA+/YwJg2FGoA==
|
||||
</X509Certificate>
|
||||
</X509Data>
|
||||
</KeyInfo>
|
||||
<EncryptionMethod Algorithm=
|
||||
"https://www.w3.org/2001/04/xmlenc#aes128-cbc">
|
||||
<KeySize xmlns="https://www.w3.org/2001/04/xmlenc#">128</KeySize>
|
||||
</EncryptionMethod>
|
||||
</KeyDescriptor>
|
||||
<SingleLogoutService
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"
|
||||
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"/>
|
||||
<SingleLogoutService
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloSoap/metaAlias/sp"/>
|
||||
<ManageNameIDService
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"
|
||||
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"/>
|
||||
<ManageNameIDService
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||
Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"
|
||||
ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"/>
|
||||
<NameIDFormat>
|
||||
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
|
||||
</NameIDFormat>
|
||||
<NameIDFormat>
|
||||
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
||||
</NameIDFormat>
|
||||
<AssertionConsumerService
|
||||
isDefault="true"
|
||||
index="0"
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
||||
Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
|
||||
<AssertionConsumerService
|
||||
index="1"
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
|
||||
</SPSSODescriptor>
|
||||
</EntityDescriptor>
|
|
@ -30,7 +30,6 @@ import org.junit.Test;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.adapter.AdapterTestStrategy;
|
||||
import org.keycloak.testsuite.keycloaksaml.SamlAdapterTestStrategy;
|
||||
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
@ -73,7 +72,7 @@ public class TomcatSamlTest {
|
|||
tomcat.deploySaml("/bad-client-sales-post-sig", "bad-client-signed-post");
|
||||
tomcat.deploySaml("/bad-realm-sales-post-sig", "bad-realm-signed-post");
|
||||
tomcat.deploySaml("/sales-post-enc", "encrypted-post");
|
||||
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth", keycloakRule);
|
||||
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth");
|
||||
|
||||
|
||||
tomcat.start();
|
||||
|
|
Loading…
Reference in a new issue