Skip to content

Commit

Permalink
saml broker import/export, and module fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
patriot1burke committed Mar 17, 2015
1 parent 13b22d6 commit ce2c418
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 29 deletions.
Expand Up @@ -301,20 +301,20 @@ public Response export(UriInfo uriInfo, RealmModel realm, String format) {
authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(); authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
} }


String assertionConsumerService = uriInfo.getBaseUriBuilder().path("realms").path(realm.getName()).path("broker").path(getConfig().getProviderId()).build().toString(); String assertionConsumerService = uriInfo.getBaseUriBuilder().path("realms").path(realm.getName()).path("broker").path(getConfig().getId()).build().toString();






String descriptor = String descriptor =
"<EntityDescriptor entityID=\"" + getEntityId(uriInfo, realm) + "\n" + "<EntityDescriptor entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
" <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\n" + " <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\n" +
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" + " protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" + " <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
" </NameIDFormat>\n" + " </NameIDFormat>\n" +
// todo single logout service description // todo single logout service description
// " <SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8081/sales-metadata/\"/>\n" + // " <SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8081/sales-metadata/\"/>\n" +
" <AssertionConsumerService\n" + " <AssertionConsumerService\n" +
" Binding=\"" + authnBinding + "\" Location=\"" + assertionConsumerService + "\n" + " Binding=\"" + authnBinding + "\" Location=\"" + assertionConsumerService + "\"\n" +
" index=\"1\" isDefault=\"true\" />\n"; " index=\"1\" isDefault=\"true\" />\n";
if (getConfig().isWantAuthnRequestsSigned()) { if (getConfig().isWantAuthnRequestsSigned()) {
descriptor += descriptor +=
Expand Down
22 changes: 18 additions & 4 deletions broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java 100644 → 100755
Expand Up @@ -19,9 +19,12 @@


import org.keycloak.broker.provider.AbstractIdentityProviderFactory; import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderModel;
import org.picketlink.common.constants.JBossSAMLConstants;
import org.picketlink.common.constants.JBossSAMLURIConstants;
import org.picketlink.common.exceptions.ParsingException; import org.picketlink.common.exceptions.ParsingException;
import org.picketlink.common.util.DocumentUtil; import org.picketlink.common.util.DocumentUtil;
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser; import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
import org.picketlink.identity.federation.saml.v2.metadata.EndpointType;
import org.picketlink.identity.federation.saml.v2.metadata.EntitiesDescriptorType; import org.picketlink.identity.federation.saml.v2.metadata.EntitiesDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.EntityDescriptorType; import org.picketlink.identity.federation.saml.v2.metadata.EntityDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.IDPSSODescriptorType; import org.picketlink.identity.federation.saml.v2.metadata.IDPSSODescriptorType;
Expand Down Expand Up @@ -53,7 +56,7 @@ public SAMLIdentityProvider create(IdentityProviderModel model) {
} }


@Override @Override
public Map<String, String> parseConfig(InputStream inputStream) { public Map<String, String> parseConfig(InputStream inputStream) {
try { try {
Object parsedObject = new SAMLParser().parse(inputStream); Object parsedObject = new SAMLParser().parse(inputStream);
EntityDescriptorType entityType; EntityDescriptorType entityType;
Expand All @@ -76,11 +79,22 @@ public Map<String, String> parseConfig(InputStream inputStream) {


if (idpDescriptor != null) { if (idpDescriptor != null) {
SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig(); SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig();

String singleSignOnServiceUrl = null;
samlIdentityProviderConfig.setSingleSignOnServiceUrl(idpDescriptor.getSingleSignOnService().get(0).getLocation().toString()); boolean postBinding = false;
for (EndpointType endpoint : idpDescriptor.getSingleSignOnService()) {
if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
singleSignOnServiceUrl = endpoint.getLocation().toString();
postBinding = true;
break;
} else if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
singleSignOnServiceUrl = endpoint.getLocation().toString();
}
}
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned()); samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned()); samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
samlIdentityProviderConfig.setPostBindingResponse(true); samlIdentityProviderConfig.setPostBindingResponse(postBinding);
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);


List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor(); List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
String defaultCertificate = null; String defaultCertificate = null;
Expand Down
Expand Up @@ -5,9 +5,14 @@
<!-- Insert resources here --> <!-- Insert resources here -->
</resources> </resources>
<dependencies> <dependencies>
<module name="javax.api" />
<module name="org.keycloak.keycloak-core"/> <module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/> <module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-broker-core"/> <module name="org.keycloak.keycloak-broker-core"/>
<module name="org.keycloak.keycloak-saml-protocol"/>
<module name="org.picketlink.common"/> <module name="org.picketlink.common"/>
<module name="org.picketlink.federation"/>
<module name="javax.ws.rs.api"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
</dependencies> </dependencies>
</module> </module>
Expand Up @@ -638,7 +638,7 @@ module.controller('RealmDefaultRolesCtrl', function ($scope, Realm, realm, appli


}); });


module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, realm, instance, providerFactory, IdentityProvider, serverInfo, $location, Notifications) { module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, realm, instance, providerFactory, IdentityProvider, serverInfo, $location, Notifications) {
console.log('RealmIdentityProviderCtrl'); console.log('RealmIdentityProviderCtrl');


$scope.realm = angular.copy(realm); $scope.realm = angular.copy(realm);
Expand Down Expand Up @@ -678,13 +678,15 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload


$scope.files = []; $scope.files = [];
$scope.importFile = false; $scope.importFile = false;
$scope.importUrl = false;


$scope.onFileSelect = function($files) { $scope.onFileSelect = function($files) {
$scope.importFile = true; $scope.importFile = true;
$scope.files = $files; $scope.files = $files;
}; };


$scope.clearFileSelect = function() { $scope.clearFileSelect = function() {
$scope.importUrl = false;
$scope.importFile = false; $scope.importFile = false;
$scope.files = null; $scope.files = null;
} }
Expand All @@ -694,7 +696,7 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
for (var i = 0; i < $scope.files.length; i++) { for (var i = 0; i < $scope.files.length; i++) {
var $file = $scope.files[i]; var $file = $scope.files[i];
$scope.upload = $upload.upload({ $scope.upload = $upload.upload({
url: authUrl + '/admin/realms/' + realm.realm + '/identity-provider/', url: authUrl + '/admin/realms/' + realm.realm + '/identity-provider/import',
// method: POST or PUT, // method: POST or PUT,
// headers: {'headerKey': 'headerValue'}, withCredential: true, // headers: {'headerKey': 'headerValue'}, withCredential: true,
data: $scope.identityProvider, data: $scope.identityProvider,
Expand All @@ -714,6 +716,24 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
} }
}; };


$scope.importFrom = function() {
$scope.identityProvider.fromUrl = $scope.fromUrl;
$http.post(authUrl + '/admin/realms/' + realm.realm + '/identity-provider/import', $scope.identityProvider)
.success(function(data, status, headers) {
$location.url("/realms/" + realm.realm + "/identity-provider-settings");
Notifications.success("The " + $scope.identityProvider.name + " provider has been created.");
}).error(function() {
Notifications.error("The provider can not be imported. Please verify the url.");
});
};
$scope.$watch('fromUrl', function(newVal, oldVal){
if ($scope.fromUrl && $scope.fromUrl.length > 0) {
$scope.importUrl = true;
} else{
$scope.importUrl = false;
}
});

$scope.$watch('configuredProviders', function(configuredProviders) { $scope.$watch('configuredProviders', function(configuredProviders) {
if (configuredProviders) { if (configuredProviders) {
$scope.configuredProviders = angular.copy(configuredProviders); $scope.configuredProviders = angular.copy(configuredProviders);
Expand Down
Expand Up @@ -1102,7 +1102,7 @@ module.factory('PasswordPolicy', function() {
}); });


module.factory('IdentityProvider', function($resource) { module.factory('IdentityProvider', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/identity-provider/:id', { return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:id', {
realm : '@realm' realm : '@realm'
}, { }, {
create : { create : {
Expand All @@ -1118,7 +1118,7 @@ module.factory('IdentityProvider', function($resource) {
}); });


module.factory('IdentityProviderExport', function($resource) { module.factory('IdentityProviderExport', function($resource) {
var url = authUrl + '/admin/realms/:realm/identity-provider/:id/export'; var url = authUrl + '/admin/realms/:realm/identity-provider/instances/:id/export';
return { return {
url : function(parameters) url : function(parameters)
{ {
Expand Down
Expand Up @@ -25,8 +25,15 @@ <h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>
</div> </div>
<span tooltip-placement="right" tooltip="The friendly name for this identity provider." class="fa fa-info-circle"></span> <span tooltip-placement="right" tooltip="The friendly name for this identity provider." class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group" data-ng-show="newIdentityProvider"> <div class="form-group" data-ng-show="newIdentityProvider && !importFile">
<label class="col-sm-2 control-label">Import IdP SAML Metadata </label> <label class="col-sm-2 control-label" for="fromUrl">Import From Url</label>
<div class="col-sm-4">
<input class="form-control" id="fromUrl" type="text" ng-model="fromUrl">
</div>
<span tooltip-placement="right" tooltip="Import metadata from a remote IDP SAML entity descriptor." class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="newIdentityProvider && !importUrl">
<label class="col-sm-2 control-label">Import From File</label>
<div class="col-sm-4"> <div class="col-sm-4">
<div class="controls kc-button-input-file" data-ng-show="!files || files.length == 0"> <div class="controls kc-button-input-file" data-ng-show="!files || files.length == 0">
<a href="#" class="btn btn-default"><span class="kc-icon-upload">Icon: Upload</span>Choose a File...</a> <a href="#" class="btn btn-default"><span class="kc-icon-upload">Icon: Upload</span>Choose a File...</a>
Expand All @@ -37,14 +44,14 @@ <h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>
</span> </span>
</div> </div>
</div> </div>
<div class="form-group clearfix" data-ng-show="!importFile"> <div class="form-group clearfix" data-ng-show="!importFile && !importUrl">
<label class="col-sm-2 control-label" for="singleSignOnServiceUrl">Single Sign-On Service Url<span class="required">*</span></label> <label class="col-sm-2 control-label" for="singleSignOnServiceUrl">Single Sign-On Service Url<span class="required">*</span></label>
<div class="col-sm-4"> <div class="col-sm-4">
<input class="form-control" id="singleSignOnServiceUrl" type="text" ng-model="identityProvider.config.singleSignOnServiceUrl" required> <input class="form-control" id="singleSignOnServiceUrl" type="text" ng-model="identityProvider.config.singleSignOnServiceUrl" required>
</div> </div>
<span tooltip-placement="right" tooltip="The Url that must be used to send authentication requests(SAML AuthnRequest)." class="fa fa-info-circle"></span> <span tooltip-placement="right" tooltip="The Url that must be used to send authentication requests(SAML AuthnRequest)." class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group clearfix" data-ng-show="!importFile"> <div class="form-group clearfix" data-ng-show="!importFile && !importUrl">
<label class="col-sm-2 control-label" for="nameIDPolicyFormat">NameID Policy Format</label> <label class="col-sm-2 control-label" for="nameIDPolicyFormat">NameID Policy Format</label>
<div class="col-sm-4"> <div class="col-sm-4">
<select id="nameIDPolicyFormat" ng-model="identityProvider.config.nameIDPolicyFormat"> <select id="nameIDPolicyFormat" ng-model="identityProvider.config.nameIDPolicyFormat">
Expand All @@ -60,42 +67,42 @@ <h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>
</div> </div>
<span tooltip-placement="right" tooltip="Specifies the URI reference corresponding to a name identifier format. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:persistent." class="fa fa-info-circle"></span> <span tooltip-placement="right" tooltip="Specifies the URI reference corresponding to a name identifier format. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:persistent." class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group clearfix" data-ng-show="!importFile"> <div class="form-group clearfix" data-ng-show="!importFile && !importUrl">
<label class="col-sm-2 control-label" for="signingCertificate">Validating X509 Certificate</label> <label class="col-sm-2 control-label" for="signingCertificate">Validating X509 Certificate</label>
<div class="col-sm-4"> <div class="col-sm-4">
<textarea class="form-control" id="signingCertificate" ng-model="identityProvider.config.signingCertificate"/> <textarea class="form-control" id="signingCertificate" ng-model="identityProvider.config.signingCertificate"/>
</div> </div>
<span tooltip-placement="right" tooltip="The certificate in PEM format that must be used to check for signatures." class="fa fa-info-circle"></span> <span tooltip-placement="right" tooltip="The certificate in PEM format that must be used to check for signatures." class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group" data-ng-show="!importFile"> <div class="form-group" data-ng-show="!importFile && !importUrl">
<label class="col-sm-2 control-label" for="wantAuthnRequestsSigned">Want AuthnRequests Signed</label> <label class="col-sm-2 control-label" for="wantAuthnRequestsSigned">Want AuthnRequests Signed</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input ng-model="identityProvider.config.wantAuthnRequestsSigned" id="wantAuthnRequestsSigned" onoffswitch /> <input ng-model="identityProvider.config.wantAuthnRequestsSigned" id="wantAuthnRequestsSigned" onoffswitch />
</div> </div>
<span tooltip-placement="right" tooltip=" Indicates whether the identity provider expects signed a AuthnRequest." class="fa fa-info-circle"></span> <span tooltip-placement="right" tooltip=" Indicates whether the identity provider expects signed a AuthnRequest." class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group" data-ng-show="!importFile"> <div class="form-group" data-ng-show="!importFile && !importUrl">
<label class="col-sm-2 control-label" for="forceAuthn">Force Authentication</label> <label class="col-sm-2 control-label" for="forceAuthn">Force Authentication</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input ng-model="identityProvider.config.forceAuthn" id="forceAuthn" onoffswitch /> <input ng-model="identityProvider.config.forceAuthn" id="forceAuthn" onoffswitch />
</div> </div>
<span tooltip-placement="right" tooltip=" Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context." class="fa fa-info-circle"></span> <span tooltip-placement="right" tooltip=" Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context." class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group" data-ng-show="!importFile"> <div class="form-group" data-ng-show="!importFile && !importUrl">
<label class="col-sm-2 control-label" for="validateSignature">Validate Signature</label> <label class="col-sm-2 control-label" for="validateSignature">Validate Signature</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input ng-model="identityProvider.config.validateSignature" id="validateSignature" onoffswitch /> <input ng-model="identityProvider.config.validateSignature" id="validateSignature" onoffswitch />
</div> </div>
<span tooltip-placement="right" tooltip="Enable/disable signature validation of SAML responses." class="fa fa-info-circle"></span> <span tooltip-placement="right" tooltip="Enable/disable signature validation of SAML responses." class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group" data-ng-show="!importFile"> <div class="form-group" data-ng-show="!importFile && !importUrl">
<label class="col-sm-2 control-label" for="postBindingResponse">HTTP-POST Binding Response</label> <label class="col-sm-2 control-label" for="postBindingResponse">HTTP-POST Binding Response</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input ng-model="identityProvider.config.postBindingResponse" id="postBindingResponse" onoffswitch /> <input ng-model="identityProvider.config.postBindingResponse" id="postBindingResponse" onoffswitch />
</div> </div>
<span tooltip-placement="right" tooltip="Indicates whether the identity provider must respond to the AuthnRequest using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used." class="fa fa-info-circle"></span> <span tooltip-placement="right" tooltip="Indicates whether the identity provider must respond to the AuthnRequest using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used." class="fa fa-info-circle"></span>
</div> </div>
<div class="form-group" data-ng-show="!importFile"> <div class="form-group" data-ng-show="!importFile && !importUrl">
<label class="col-sm-2 control-label" for="postBindingAuthnRequest">HTTP-POST Binding for AuthnRequest</label> <label class="col-sm-2 control-label" for="postBindingAuthnRequest">HTTP-POST Binding for AuthnRequest</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input ng-model="identityProvider.config.postBindingAuthnRequest" id="postBindingAuthnRequest" onoffswitch /> <input ng-model="identityProvider.config.postBindingAuthnRequest" id="postBindingAuthnRequest" onoffswitch />
Expand Down Expand Up @@ -134,9 +141,10 @@ <h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>


<div class="pull-right form-actions"> <div class="pull-right form-actions">
<a class="btn btn-lg btn-primary" href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.id}}/export" data-ng-show="!importFile && !newIdentityProvider">Export</a> <a class="btn btn-lg btn-primary" href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.id}}/export" data-ng-show="!importFile && !newIdentityProvider">Export</a>
<button kc-save data-ng-show="!importFile">Save</button> <button kc-save data-ng-show="!importFile && !importUrl">Save</button>
<button type="submit" data-ng-click="clearFileSelect()" data-ng-show="importFile" class="btn btn-lg btn-default">Cancel</button> <button type="submit" data-ng-click="clearFileSelect()" data-ng-show="importFile || importUrl" class="btn btn-lg btn-default">Cancel</button>
<button type="submit" data-ng-click="uploadFile()" data-ng-show="importFile" class="btn btn-lg btn-primary">Import from SAML Metadata</button> <button type="submit" data-ng-click="uploadFile()" data-ng-show="importFile" class="btn btn-lg btn-primary">Import</button>
<button type="submit" data-ng-click="importFrom()" data-ng-show="importUrl" class="btn btn-lg btn-primary">Import</button>
<button kc-delete data-ng-click="remove()" data-ng-show="!newIdentityProvider">Delete</button> <button kc-delete data-ng-click="remove()" data-ng-show="!newIdentityProvider">Delete</button>
</div> </div>
</form> </form>
Expand Down
Expand Up @@ -6,8 +6,13 @@
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.PUT; import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;


/** /**
* @author pedroigor * @author pedroigor
Expand All @@ -24,4 +29,9 @@ public interface IdentityProviderResource {


@DELETE @DELETE
void remove(); void remove();

@GET
@Path("export")
public Response export(@QueryParam("format") String format);

} }
Expand Up @@ -16,7 +16,7 @@
*/ */
public interface IdentityProvidersResource { public interface IdentityProvidersResource {


@Path("{id}") @Path("instances/{id}")
IdentityProviderResource get(@PathParam("id") String id); IdentityProviderResource get(@PathParam("id") String id);


@GET @GET
Expand Down

0 comments on commit ce2c418

Please sign in to comment.