diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index ebb957f3762..b0a07b74225 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -32,7 +32,7 @@ Here is a summary of the different scopes that are known to the UAA. * **idps.read** - read only scopes to retrieve identity providers under /identity-providers * **idps.write** - read only scopes to retrieve identity providers under /identity-providers * **clients.admin** - super user scope to create, modify and delete clients -* **clients.write** - scope required to create and modify clients. The scopes/authorities are limited to be prefixed with the scope holder's client id. For example, id:testclient authorities:client.write may create a client that has scopes/authorities that have the 'testclient.' prefix. +* **clients.write** - scope required to create and modify clients. The scopes are limited to be prefixed with the scope holder's client id. For example, id:testclient authorities:client.write may create a client that has scopes that have the 'testclient.' prefix. Authorities are limited to uaa.resource * **clients.read** - scope to read information about clients * **clients.secret** - ``/oauth/clients/*/secret`` endpoint. Scope required to change the password of a client. Considered an admin scope. * **scim.write** - Admin write access to all SCIM endpoints, ``/Users``, ``/Groups/``. @@ -656,6 +656,49 @@ Notes: .. _oauth2 token endpoint: + +OAuth2 Token Revocation Service/Client: ``GET /oauth/token/revoke/client/{client-id}`` +-------------------------------------------------------------------------------------- + +An endpoint that allows all tokens for a specific client to be revoked +* Request: uses token authorization and requires `uaa.admin` scope:: + + GET /oauth/token/revoke/client/{client-id} HTTP/1.1 + Host: server.example.com + Authorization: Bearer token + +* Successful Response:: + + HTTP/1.1 200 OK + +* Error Response:: + + HTTP/1.1 401 Unauthorized - Authentication is not sufficient + HTTP/1.1 403 Forbidden - Authenticated, but uaa.admin scope is not present + HTTP/1.1 404 Not Found - Client ID is invalid + +OAuth2 Token Revocal Service/User: ``GET /oauth/token/revoke/user/{user-id}`` +----------------------------------------------------------------------------- + +An endpoint that allows all tokens for a specific user to be revoked +* Request: uses token authorization and requires `uaa.admin` scope:: + + GET /oauth/token/revoke/client/{client-id} HTTP/1.1 + Host: server.example.com + Authorization: Bearer token + +* Successful Response:: + + HTTP/1.1 200 OK + +* Error Response:: + + HTTP/1.1 401 Unauthorized - Authentication is not sufficient + HTTP/1.1 403 Forbidden - Authenticated, but uaa.admin scope is not present + HTTP/1.1 404 Not Found - User ID is invalid + + + OpenID User Info Endpoint: ``GET /userinfo`` -------------------------------------------- @@ -811,20 +854,20 @@ Fields *Available Fields* :: tokenPolicy TokenPolicy Optional Various fields pertaining to the JWT access and refresh tokens. See `Token Policy` section below for details. samlConfig SamlConfig Optional Various fields pertaining to SAML identity provider configuration. See ``SamlConfig`` section below for details. - Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.TokenPolicy) - ===================== ==================== ======== ======================================================================================================================================================================== - accessTokenValidity int Optional How long the access token is valid for in seconds. - refreshTokenValidity int Optional How long the refresh token is valid for seconds. + Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.TokenPolicy) + ===================== ==================== ======== ======================================================================================================================================================================== + accessTokenValidity int Optional How long the access token is valid for in seconds. + refreshTokenValidity int Optional How long the refresh token is valid for seconds. - SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) - ===================== ==================== ======== ======================================================================================================================================================================== - requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``true``. - wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``true``. - certificate String Optional Exposed SAML metadata property. The certificate used to sign all communications. Reserved for future use. - privateKey String Optional Exposed SAML metadata property. The SAML provider's private key. Reserved for future use. - privateKeyPassword String Optional Exposed SAML metadata property. The SAML provider's private key password. Reserved for future use. + SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) + ===================== ==================== ======== ======================================================================================================================================================================== + requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``true``. + wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``true``. + certificate String Optional Exposed SAML metadata property. The certificate used to sign all communications. Reserved for future use. + privateKey String Optional Exposed SAML metadata property. The SAML provider's private key. Reserved for future use. + privateKeyPassword String Optional Exposed SAML metadata property. The SAML provider's private key password. Reserved for future use. - ===================== ==================== ======== ======================================================================================================================================================================== + ===================== ==================== ======== ======================================================================================================================================================================== Curl Example POST (Token contains ``zones.write`` scope) :: @@ -1229,6 +1272,7 @@ Fields *Available Fields* :: countFailuresWithin int Required Amount of time in seconds for which past login failures are counted, starting from the current time, 0+ disableInternalUserManagement boolean Optional When set to true, user management is disabled for this provider, defaults to false emailDomain List Optional List of email domains associated with the UAA provider. If null and no domains are explicitly matched with any other providers, the UAA acts as a catch-all, wherein the email will be associated with the UAA provider. Wildcards supported. + providerDescription String Optional Human readable name/description of this provider SAML Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition ====================== ====================== ======== ================================================================================================================================================================================================================================================================================================================================================================================================================================================= @@ -1244,6 +1288,7 @@ Fields *Available Fields* :: emailDomain List Optional List of email domains associated with the SAML provider for the purpose of associating users to the correct origin upon invitation. If null or empty list, no invitations are accepted. Wildcards supported. attributeMappings Map Optional List of UAA attributes mapped to attributes in the SAML assertion. Currently we support mapping given_name, family_name, email, phone_number and external_groups. Also supports custom user attributes to be populated in the id_token when the `user_attributes` scope is requested. The attributes are pulled out of the user records and have the format `user.attribute.: ` externalGroupsWhitelist List Optional List of external groups that will be included in the ID Token if the `roles` scope is requested. + providerDescription String Optional Human readable name/description of this provider LDAP Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition ====================== ====================== ======== ================================================================================================================================================================================================= @@ -1266,6 +1311,7 @@ Fields *Available Fields* :: emailDomain List Optional List of email domains associated with the LDAP provider for the purpose of associating users to the correct origin upon invitation. If null or empty list, no invitations are accepted. Wildcards supported. attributeMappings Map Optional List of UAA attributes mapped to attributes from LDAP. Currently we support mapping given_name, family_name, email, phone_number and external_groups. externalGroupsWhitelist List Optional List of external groups (`DN` distinguished names`) that can be included in the ID Token if the `roles` scope is requested. See `UAA-LDAP.md UAA-LDAP.md`_ for more information + providerDescription String Optional Human readable name/description of this provider Curl Example POST (Creating a SAML provider):: @@ -1383,6 +1429,7 @@ Response body *example* (a provider contains the fields defined above) :: 400 - Bad Request - Invalid configuration - result contains stack trace 403 - Forbidden - insufficient scope 500 - Internal Server Error - error information will only be in server logs + ================ ========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== @@ -1980,10 +2027,11 @@ __ http://www.simplecloud.info/specs/draft-scim-api-01.html#create-resource { "displayName":"uaa.admin", "members":[ - { "type":"USER","authorities":["READ"],"value":"3ebe4bda-74a2-40c4-8b70-f771d9bc8b9f","origin":"uaa" } - ] + { "type":"USER","authorities":["READ"],"value":"3ebe4bda-74a2-40c4-8b70-f771d9bc8b9f","origin":"uaa" } + ] } + The ``displayName`` is unique in the UAA, but is allowed to change. Each group also has a fixed primary key which is a UUID (stored in the ``id`` field of the core schema). The origin value shows what identity provider was responsible for making the connection between the user and the group. For example, if this relationship came from an LDAP user, it would have origin=ldap. @@ -2033,7 +2081,8 @@ See `SCIM - Modifying with PUT - + (optional) ``If-Match`` the ``ETag`` (version id) for the value to update + + (optional) ``If-Match`` the ``ETag`` (version id) for the value to update + * Request Body:: Host: example.com @@ -2279,10 +2328,10 @@ The API ``GET /Groups/External/list`` is deprecated "itemsPerPage":100, "totalResults":5, "schemas":["urn:scim:schemas:core:1.0"] - } + } - * Response Codes:: +* Response Codes:: 200 - Results retrieved successfully 401 - Unauthorized @@ -2459,6 +2508,7 @@ Response body *example* :: "n":"ANJufZdrvYg5zG61x36pDq59nVUN73wSanA7hVCtN3ftT2Rm1ZTQqp5KSCfLMhaaVvJY51sHj+/i4lqUaM9CO32G93fE44VfOmPfexZeAwa8YDOikyTrhP7sZ6A4WUNeC4DlNnJF4zsznU7JxjCkASwpdL6XFwbRSzGkm6b9aM4vIewyclWehJxUGVFhnYEzIQ65qnr38feVP9enOVgQzpKsCJ+xpa8vZ/UrscoG3/IOQM6VnLrGYAyyCGeyU1JXQW/KlNmtA5eJry2Tp+MD6I34/QsNkCArHOfj8H9tXz/oc3/tVkkR252L/Lmp0TtIGfHpBmoITP9h+oKiW6NpyCc=", "e":"AQAB" } + ================ ======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================= The algorithm ("alg") tells the caller how to use the value (it is the @@ -2897,61 +2947,20 @@ Response Headers :: ================== =============================================== +SAML Service Provider (SP) Metadata: ``GET /saml/metadata`` +----------------------------------------------------------- -Management Endpoints -==================== +================== =============================================== +Request ``GET /saml/metadata`` +Response Code ``200 - Found`` +Response Headers :: -Basic Metrics: ``GET /varz`` ----------------------------- + Content-Type: text/html;charset=utf-8 -Authentication is via HTTP basic using credentials that are configured -via ``varz.username`` and ``varz.password``. The ``/varz`` endpoint pulls -data out of the JMX ``MBeanServer``, exposing selected nuggets directly -for ease of use, and providing links to more detailed metrics. +================== =============================================== + + +Management Endpoints +==================== -* Request: ``GET /varz`` -* Response Body:: - { - "type": "UAA", - "links": { - "Users": "http://localhost:8080/uaa/varz/Users", - "JMImplementation": "http://localhost:8080/uaa/varz/JMImplementation", - "spring.application": "http://localhost:8080/uaa/varz/spring.application", - "com.sun.management": "http://localhost:8080/uaa/varz/com.sun.management", - "Catalina": "http://localhost:8080/uaa/varz/Catalina", - "env": "http://localhost:8080/uaa/varz/env", - "java.lang": "http://localhost:8080/uaa/varz/java.lang", - "java.util.logging": "http://localhost:8080/uaa/varz/java.util.logging" - }, - "mem": 19173496, - "memory": { - "verbose": false, - "non_heap_memory_usage": { - "max": 184549376, - "committed": 30834688, - "init": 19136512, - "used": 30577744 - }, - "object_pending_finalization_count": 0, - "heap_memory_usage": { - "max": 902299648, - "committed": 84475904, - "init": 63338496, - "used": 19173496 - } - }, - "token_store": { - "refresh_token_count": 0, - "access_token_count": 0, - "flush_interval": 1000 - }, - "audit_service": { - "user_authentication_count": 0, - "user_not_found_count": 0, - "principal_authentication_failure_count": 1, - "principal_not_found_count": 0, - "user_authentication_failure_count": 0 - }, - "spring.profiles.active": [] - } diff --git a/gradle.properties b/gradle.properties index 4950f0ded39..bad42edb837 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=3.0.0 +version=3.0.1 diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadata.java b/model/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadata.java new file mode 100644 index 00000000000..360c99458ba --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadata.java @@ -0,0 +1,70 @@ +package org.cloudfoundry.identity.uaa.client; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.net.URL; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ClientMetadata { + + private String clientId; + private String identityZoneId; + private boolean showOnHomePage; + private URL appLaunchUrl; + private String appIcon; + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + @JsonIgnore + public String getIdentityZoneId() { + return identityZoneId; + } + + @JsonIgnore + public void setIdentityZoneId(String identityZoneId) { + this.identityZoneId = identityZoneId; + } + + public boolean isShowOnHomePage() { + return showOnHomePage; + } + + public void setShowOnHomePage(boolean showOnHomePage) { + this.showOnHomePage = showOnHomePage; + } + + public URL getAppLaunchUrl() { + return appLaunchUrl; + } + + public void setAppLaunchUrl(URL appLaunchUrl) { + this.appLaunchUrl = appLaunchUrl; + } + + public String getAppIcon() { + return appIcon; + } + + public void setAppIcon(String appIcon) { + this.appIcon = appIcon; + } +} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java index 8f6698cc1f1..b2d00fbb1ee 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java @@ -19,9 +19,11 @@ public class AbstractIdentityProviderDefinition { public static final String EMAIL_DOMAIN_ATTR = "emailDomain"; + public static final String PROVIDER_DESCRIPTION = "providerDescription"; private List emailDomain; private Map additionalConfiguration; + private String providerDescription; public List getEmailDomain() { return emailDomain; @@ -41,6 +43,14 @@ public AbstractIdentityProviderDefinition setAdditionalConfiguration(Map emailDomain = getEmailDomain() != null ? new ArrayList<>(getEmailDomain()) : null; List externalGroupsWhitelist = getExternalGroupsWhitelist() != null ? new ArrayList<>(getExternalGroupsWhitelist()) : null; Map attributeMappings = getAttributeMappings() != null ? new HashMap(getAttributeMappings()) : null; - return Builder.get() - .setMetaDataLocation(metaDataLocation) - .setIdpEntityAlias(idpEntityAlias) - .setNameID(nameID) - .setAssertionConsumerIndex(assertionConsumerIndex) - .setMetadataTrustCheck(metadataTrustCheck) - .setShowSamlLink(showSamlLink) - .setLinkText(linkText) - .setIconUrl(iconUrl) - .setZoneId(zoneId) - .setAddShadowUserOnLogin(addShadowUserOnLogin) - .setEmailDomain(emailDomain) - .setExternalGroupsWhitelist(externalGroupsWhitelist) - .setAttributeMappings(attributeMappings) - .build(); + SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); + def.setMetaDataLocation(metaDataLocation); + def.setIdpEntityAlias(idpEntityAlias); + def.setZoneId(zoneId); + def.setNameID(nameID); + def.setAssertionConsumerIndex(assertionConsumerIndex); + def.setMetadataTrustCheck(metadataTrustCheck); + def.setShowSamlLink(showSamlLink); + def.setLinkText(linkText); + def.setIconUrl(iconUrl); + def.setAddShadowUserOnLogin(addShadowUserOnLogin); + def.setEmailDomain(emailDomain); + def.setExternalGroupsWhitelist(externalGroupsWhitelist); + def.setAttributeMappings(attributeMappings); + def.setAdditionalConfiguration(getAdditionalConfiguration()); + def.setProviderDescription(getProviderDescription()); + return def; } @JsonIgnore @@ -115,48 +117,54 @@ public String getMetaDataLocation() { return metaDataLocation; } - public void setMetaDataLocation(String metaDataLocation) { + public SamlIdentityProviderDefinition setMetaDataLocation(String metaDataLocation) { this.metaDataLocation = metaDataLocation; + return this; } public String getIdpEntityAlias() { return idpEntityAlias; } - public void setIdpEntityAlias(String idpEntityAlias) { + public SamlIdentityProviderDefinition setIdpEntityAlias(String idpEntityAlias) { this.idpEntityAlias = idpEntityAlias; + return this; } public String getNameID() { return nameID; } - public void setNameID(String nameID) { + public SamlIdentityProviderDefinition setNameID(String nameID) { this.nameID = nameID; + return this; } public int getAssertionConsumerIndex() { return assertionConsumerIndex; } - public void setAssertionConsumerIndex(int assertionConsumerIndex) { + public SamlIdentityProviderDefinition setAssertionConsumerIndex(int assertionConsumerIndex) { this.assertionConsumerIndex = assertionConsumerIndex; + return this; } public boolean isMetadataTrustCheck() { return metadataTrustCheck; } - public void setMetadataTrustCheck(boolean metadataTrustCheck) { + public SamlIdentityProviderDefinition setMetadataTrustCheck(boolean metadataTrustCheck) { this.metadataTrustCheck = metadataTrustCheck; + return this; } public boolean isShowSamlLink() { return showSamlLink; } - public void setShowSamlLink(boolean showSamlLink) { + public SamlIdentityProviderDefinition setShowSamlLink(boolean showSamlLink) { this.showSamlLink = showSamlLink; + return this; } public String getSocketFactoryClassName() { @@ -173,8 +181,7 @@ public String getSocketFactoryClassName() { } } - public void setSocketFactoryClassName(String socketFactoryClassName) { - this.socketFactoryClassName = socketFactoryClassName; + public SamlIdentityProviderDefinition setSocketFactoryClassName(String socketFactoryClassName) { if (socketFactoryClassName!=null && socketFactoryClassName.trim().length()>0) { try { Class.forName( @@ -188,38 +195,44 @@ public void setSocketFactoryClassName(String socketFactoryClassName) { throw new IllegalArgumentException(e); } } + this.socketFactoryClassName = socketFactoryClassName; + return this; } public String getLinkText() { return StringUtils.hasText(linkText) ? linkText : idpEntityAlias; } - public void setLinkText(String linkText) { + public SamlIdentityProviderDefinition setLinkText(String linkText) { this.linkText = linkText; + return this; } public String getIconUrl() { return iconUrl; } - public void setIconUrl(String iconUrl) { + public SamlIdentityProviderDefinition setIconUrl(String iconUrl) { this.iconUrl = iconUrl; + return this; } public String getZoneId() { return zoneId; } - public void setZoneId(String zoneId) { + public SamlIdentityProviderDefinition setZoneId(String zoneId) { this.zoneId = zoneId; + return this; } public boolean isAddShadowUserOnLogin() { return addShadowUserOnLogin; } - public void setAddShadowUserOnLogin(boolean addShadowUserOnLogin) { + public SamlIdentityProviderDefinition setAddShadowUserOnLogin(boolean addShadowUserOnLogin) { this.addShadowUserOnLogin = addShadowUserOnLogin; + return this; } @Override @@ -260,111 +273,4 @@ public String toString() { '}'; } - public static class Builder { - - private String metaDataLocation; - private String idpEntityAlias; - private String zoneId; - private String nameID; - private int assertionConsumerIndex; - private boolean metadataTrustCheck; - private boolean showSamlLink; - private String linkText; - private String iconUrl; - private boolean addShadowUserOnLogin = true; - private List emailDomain; - private List externalGroupsWhitelist; - private Map attributeMappings; - - private Builder(){} - - public static Builder get() { - return new Builder(); - } - - public SamlIdentityProviderDefinition build() { - SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); - - def.setMetaDataLocation(metaDataLocation); - def.setIdpEntityAlias(idpEntityAlias); - def.setZoneId(zoneId); - def.setNameID(nameID); - def.setAssertionConsumerIndex(assertionConsumerIndex); - def.setMetadataTrustCheck(metadataTrustCheck); - def.setShowSamlLink(showSamlLink); - def.setLinkText(linkText); - def.setIconUrl(iconUrl); - def.setAddShadowUserOnLogin(addShadowUserOnLogin); - def.setEmailDomain(emailDomain); - def.setExternalGroupsWhitelist(externalGroupsWhitelist); - def.setAttributeMappings(attributeMappings); - - return def; - } - - public Builder setAttributeMappings(Map attributeMappings) { - this.attributeMappings = attributeMappings; - return this; - } - - public Builder setMetaDataLocation(String metaDataLocation) { - this.metaDataLocation = metaDataLocation; - return this; - } - - public Builder setIdpEntityAlias(String idpEntityAlias) { - this.idpEntityAlias = idpEntityAlias; - return this; - } - - public Builder setZoneId(String zoneId) { - this.zoneId = zoneId; - return this; - } - - public Builder setNameID(String nameID) { - this.nameID = nameID; - return this; - } - - public Builder setAssertionConsumerIndex(int assertionConsumerIndex) { - this.assertionConsumerIndex = assertionConsumerIndex; - return this; - } - - public Builder setMetadataTrustCheck(boolean metadataTrustCheck) { - this.metadataTrustCheck = metadataTrustCheck; - return this; - } - - public Builder setShowSamlLink(boolean showSamlLink) { - this.showSamlLink = showSamlLink; - return this; - } - - public Builder setLinkText(String linkText) { - this.linkText = linkText; - return this; - } - - public Builder setIconUrl(String iconUrl) { - this.iconUrl = iconUrl; - return this; - } - - public Builder setAddShadowUserOnLogin(boolean addShadowUserOnLogin) { - this.addShadowUserOnLogin = addShadowUserOnLogin; - return this; - } - - public Builder setEmailDomain(List emailDomain) { - this.emailDomain = emailDomain; - return this; - } - - public Builder setExternalGroupsWhitelist(List externalGroupsWhitelist) { - this.externalGroupsWhitelist = externalGroupsWhitelist; - return this; - } - } -} + } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java index 6d6908c3afe..f9621bdad24 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java @@ -1,21 +1,17 @@ /******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider; -import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; - import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) @@ -54,11 +50,11 @@ public void setLockoutPolicy(LockoutPolicy lockoutPolicy) { this.lockoutPolicy = lockoutPolicy; } - public boolean isDisableInternalUserManagement() { - return disableInternalUserManagement; - } + public boolean isDisableInternalUserManagement() { + return disableInternalUserManagement; + } - public void setDisableInternalUserManagement(boolean disableInternalUserManagement) { - this.disableInternalUserManagement = disableInternalUserManagement; - } + public void setDisableInternalUserManagement(boolean disableInternalUserManagement) { + this.disableInternalUserManagement = disableInternalUserManagement; + } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java index 7a7fc5eba92..8148a35b553 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java @@ -26,6 +26,7 @@ public class ScimGroup extends ScimCore { private String displayName; private String zoneId; + private String description; private List members; @@ -56,6 +57,14 @@ public ScimGroup setMembers(List members) { return this; } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + public ScimGroup() { this(null); } @@ -72,7 +81,13 @@ public ScimGroup(String id, String name, String zoneId) { @Override public String toString() { - return String.format("(Group id: %s, name: %s, created: %s, modified: %s, version: %s, members: %s)", getId(), - displayName, getMeta().getCreated(), getMeta().getLastModified(), getVersion(), members); + return String.format("(Group id: %s, name: %s, description: %s, created: %s, modified: %s, version: %s, members: %s)", + getId(), + displayName, + description, + getMeta().getCreated(), + getMeta().getLastModified(), + getVersion(), + members); } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java index ffd6c02d377..1e53b859902 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -52,6 +52,10 @@ public ScimGroup deserialize(JsonParser jp, DeserializationContext ctxt) throws group.setId(jp.readValueAs(String.class)); } else if ("displayname".equalsIgnoreCase(fieldName)) { group.setDisplayName(jp.readValueAs(String.class)); + } else if ("description".equalsIgnoreCase(fieldName)) { + group.setDescription(jp.readValueAs(String.class)); + } else if ("zoneId".equalsIgnoreCase(fieldName)) { + group.setZoneId(jp.readValueAs(String.class)); } else if ("meta".equalsIgnoreCase(fieldName)) { group.setMeta(jp.readValueAs(ScimMeta.class)); } else if ("schemas".equalsIgnoreCase(fieldName)) { diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java index 8382a363ad8..d25adfef8f0 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -44,11 +44,13 @@ public void serialize(ScimGroup group, JsonGenerator jgen, SerializerProvider pr } } - Map groupJson = new HashMap(); + Map groupJson = new HashMap<>(); addNonNull(groupJson, "meta", group.getMeta()); addNonNull(groupJson, "schemas", group.getSchemas()); addNonNull(groupJson, "id", group.getId()); addNonNull(groupJson, "displayName", group.getDisplayName()); + addNonNull(groupJson, "zoneId", group.getZoneId()); + addNonNull(groupJson, "description", group.getDescription()); for (Map.Entry> entry : roles.entrySet()) { addNonNull(groupJson, entry.getKey(), entry.getValue()); diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupTests.java new file mode 100644 index 00000000000..4c887c675ac --- /dev/null +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupTests.java @@ -0,0 +1,52 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.scim; + +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ScimGroupTests { + private static final String GROUP_BEFORE_DESCRIPTION = "{\"meta\":{\"version\":0,\"created\":\"2016-01-13T09:01:33.909Z\"},\"zoneId\":\"zoneId\",\"displayName\":\"name\",\"schemas\":[\"urn:scim:schemas:core:1.0\"],\"id\":\"id\"}"; + ScimGroup group; + + @Before + public void setUp() { + group = new ScimGroup("id","name","zoneId"); + } + + @Test + public void testDeSerializeWithoutDescription() { + group = JsonUtils.readValue(GROUP_BEFORE_DESCRIPTION, ScimGroup.class); + assertEquals("id", group.getId()); + assertEquals("name", group.getDisplayName()); + assertEquals("zoneId", group.getZoneId()); + assertNull(group.getDescription()); + } + + @Test + public void testSerializeWithDescription() { + group.setDescription("description"); + String json = JsonUtils.writeValueAsString(group); + group = JsonUtils.readValue(json, ScimGroup.class); + assertEquals("id", group.getId()); + assertEquals("name", group.getDisplayName()); + assertEquals("zoneId", group.getZoneId()); + assertEquals("description", group.getDescription()); + } +} \ No newline at end of file diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java index 4d510bcb0cf..f14a0d61511 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.account; +import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; @@ -24,12 +25,15 @@ import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.user.UaaAuthority; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -44,8 +48,11 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.sql.Timestamp; +import java.util.Map; import java.util.regex.Pattern; +import static org.springframework.util.StringUtils.hasText; + @Controller public class ResetPasswordController { protected final Log logger = LogFactory.getLog(getClass()); @@ -57,13 +64,15 @@ public class ResetPasswordController { private final String brand; private final Pattern emailPattern; private final ExpiringCodeStore codeStore; + private final UaaUserDatabase userDatabase; public ResetPasswordController(ResetPasswordService resetPasswordService, MessageService messageService, TemplateEngine templateEngine, UaaUrlUtils uaaUrlUtils, String brand, - ExpiringCodeStore codeStore) { + ExpiringCodeStore codeStore, + UaaUserDatabase userDatabase) { this.resetPasswordService = resetPasswordService; this.messageService = messageService; this.templateEngine = templateEngine; @@ -71,6 +80,7 @@ public ResetPasswordController(ResetPasswordService resetPasswordService, this.brand = brand; emailPattern = Pattern.compile("^\\S+@\\S+\\.\\S+$"); this.codeStore = codeStore; + this.userDatabase = userDatabase; } @RequestMapping(value = "/forgot_password", method = RequestMethod.GET) @@ -162,7 +172,7 @@ public String resetPasswordPage(Model model, @RequestParam("code") String code, @RequestParam("email") String email) { - ExpiringCode expiringCode = codeStore.retrieveCode(code); + ExpiringCode expiringCode = validateUserAndClient(codeStore.retrieveCode(code)); if (expiringCode==null) { return handleUnprocessableEntity(model, response, "message_code", "bad_code"); } else { @@ -173,6 +183,30 @@ public String resetPasswordPage(Model model, } } + public ExpiringCode validateUserAndClient(ExpiringCode code) { + if (code==null) { + logger.debug("reset_password ExpiringCode object is null. Aborting."); + return null; + } + if (!hasText(code.getData())) { + logger.debug("reset_password ExpiringCode["+code.getCode()+"] data string is null or empty. Aborting."); + return null; + } + Map data = JsonUtils.readValue(code.getData(), new TypeReference>() {}); + if (!hasText(data.get("user_id"))) { + logger.debug("reset_password ExpiringCode["+code.getCode()+"] user_id string is null or empty. Aborting."); + return null; + } + String userId = data.get("user_id"); + try { + userDatabase.retrieveUserById(userId); + } catch (UsernameNotFoundException e) { + logger.debug("reset_password ExpiringCode["+code.getCode()+"] user_id is invalid. Aborting."); + return null; + } + return code; + } + @RequestMapping(value = "/reset_password.do", method = RequestMethod.POST) public String resetPassword(Model model, @RequestParam("code") String code, diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java index 99ec3b5ed56..8b01ecb01c9 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -17,43 +17,58 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; +import org.cloudfoundry.identity.uaa.authentication.InvalidCodeException; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.authentication.InvalidCodeException; import org.cloudfoundry.identity.uaa.user.UaaAuthority; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import java.util.Map; /** * @author Dave Syer - * + * */ public class AutologinAuthenticationManager implements AuthenticationManager { private Log logger = LogFactory.getLog(getClass()); private ExpiringCodeStore codeStore; + private ClientDetailsService clientDetailsService; + private UaaUserDatabase userDatabase; public void setExpiringCodeStore(ExpiringCodeStore expiringCodeStore) { this.codeStore= expiringCodeStore; } + public void setClientDetailsService(ClientDetailsService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + + public void setUserDatabase(UaaUserDatabase userDatabase) { + this.userDatabase = userDatabase; + } + public ExpiringCode doRetrieveCode(String code) { return codeStore.retrieveCode(code); } + + @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { @@ -81,25 +96,33 @@ public Authentication authenticate(Authentication authentication) throws Authent throw new BadCredentialsException("JsonConversion error", x); } - String origin; - String userId; - String username; - String clientId; - username = codeData.get("username"); - origin = codeData.get(OriginKeys.ORIGIN); - userId = codeData.get("user_id"); - clientId = codeData.get(OAuth2Utils.CLIENT_ID); + String userId = codeData.get("user_id"); + String clientId = codeData.get(OAuth2Utils.CLIENT_ID); if (clientId == null) { throw new BadCredentialsException("Cannot redeem provided code for user, client id missing"); } + try { + clientDetailsService.loadClientByClientId(clientId); + } catch (NoSuchClientException x) { + throw new BadCredentialsException("Cannot redeem provided code for user, client is missing"); + } + + UaaUser user = null; + + try { + user = userDatabase.retrieveUserById(userId); + } catch (UsernameNotFoundException e) { + throw new BadCredentialsException("Cannot redeem provided code for user, user is missing"); + } + UaaAuthenticationDetails details = (UaaAuthenticationDetails) authentication.getDetails(); if (!clientId.equals(details.getClientId())) { throw new BadCredentialsException("Cannot redeem provided code for user, client mismatch"); } - UaaPrincipal principal = new UaaPrincipal(userId,username,null,origin,null, IdentityZoneHolder.get().getId()); + UaaPrincipal principal = new UaaPrincipal(user); return new UaaAuthentication( principal, diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrap.java index db322c1e861..b4e5d86cd33 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrap.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.client; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -26,6 +28,7 @@ import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.springframework.beans.factory.InitializingBean; +import org.springframework.http.HttpStatus; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.BaseClientDetails; @@ -48,6 +51,8 @@ public class ClientAdminBootstrap implements InitializingBean { private ClientRegistrationService clientRegistrationService; + private ClientMetadataProvisioning clientMetadataProvisioning; + private String domain = "cloudfoundry\\.com"; private boolean defaultOverride = true; @@ -194,8 +199,9 @@ private String getRedirectUris(Map map) { } private void addNewClients() throws Exception { - for (String clientId : clients.keySet()) { - Map map = clients.get(clientId); + for (Map.Entry> entry : clients.entrySet()) { + String clientId = entry.getKey(); + Map map = entry.getValue(); BaseClientDetails client = new BaseClientDetails(clientId, (String) map.get("resource-ids"), (String) map.get("scope"), (String) map.get("authorized-grant-types"), (String) map.get("authorities"), getRedirectUris(map)); @@ -226,9 +232,10 @@ private void addNewClients() throws Exception { } for (String key : Arrays.asList("resource-ids", "scope", "authorized-grant-types", "authorities", "redirect-uri", "secret", "id", "override", "access-token-validity", - "refresh-token-validity")) { + "refresh-token-validity","show-on-homepage","app-launch-url","app-icon")) { info.remove(key); } + client.setAdditionalInformation(info); try { clientRegistrationService.addClientDetails(client); @@ -244,9 +251,31 @@ private void addNewClients() throws Exception { logger.debug(e.getMessage()); } } + ClientMetadata clientMetadata = buildClientMetadata(map, clientId); + clientMetadataProvisioning.update(clientMetadata); } } + private ClientMetadata buildClientMetadata(Map map, String clientId) { + Boolean showOnHomepage = (Boolean) map.get("show-on-homepage"); + String appLaunchUrl = (String) map.get("app-launch-url"); + String appIcon = (String) map.get("app-icon"); + ClientMetadata clientMetadata = new ClientMetadata(); + clientMetadata.setClientId(clientId); + + clientMetadata.setAppIcon(appIcon); + clientMetadata.setShowOnHomePage(showOnHomepage != null && showOnHomepage); + if(StringUtils.hasText(appLaunchUrl)) { + try { + clientMetadata.setAppLaunchUrl(new URL(appLaunchUrl)); + } catch (MalformedURLException e) { + logger.info(new ClientMetadataException("Invalid app-launch-url for client " + clientId, e, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + + return clientMetadata; + } + protected boolean didPasswordChange(String clientId, String rawPassword) { if (getPasswordEncoder()!=null && clientRegistrationService instanceof ClientDetailsService) { ClientDetails existing = ((ClientDetailsService)clientRegistrationService).loadClientByClientId(clientId); @@ -256,4 +285,12 @@ protected boolean didPasswordChange(String clientId, String rawPassword) { return true; } } + + public ClientMetadataProvisioning getClientMetadataProvisioning() { + return clientMetadataProvisioning; + } + + public void setClientMetadataProvisioning(ClientMetadataProvisioning clientMetadataProvisioning) { + this.clientMetadataProvisioning = clientMetadataProvisioning; + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidator.java index 09b0ff24de0..e9a23b7f2f0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidator.java @@ -12,12 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.client; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; @@ -30,17 +24,23 @@ import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.util.Assert; import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; public class ClientAdminEndpointsValidator implements InitializingBean, ClientDetailsValidator { private final Log logger = LogFactory.getLog(getClass()); - private static final Set VALID_GRANTS = new HashSet(Arrays.asList("implicit", "password", + private static final Set VALID_GRANTS = new HashSet<>(Arrays.asList("implicit", "password", "client_credentials", "authorization_code", "refresh_token")); - private static final Collection NON_ADMIN_INVALID_GRANTS = new HashSet(Arrays.asList("password")); + private static final Collection NON_ADMIN_INVALID_GRANTS = new HashSet<>(Arrays.asList("password")); - private static final Collection NON_ADMIN_VALID_AUTHORITIES = new HashSet(Arrays.asList("uaa.none")); + private static final Collection NON_ADMIN_VALID_AUTHORITIES = new HashSet<>(Arrays.asList("uaa.none")); private QueryableResourceManager clientDetailsService; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpoints.java new file mode 100644 index 00000000000..21d8d305e06 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpoints.java @@ -0,0 +1,97 @@ +package org.cloudfoundry.identity.uaa.client; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.stereotype.Controller; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.View; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +@Controller +public class ClientMetadataAdminEndpoints { + + private ClientMetadataProvisioning clientMetadataProvisioning; + private HttpMessageConverter[] messageConverters; + + private static Log logger = LogFactory.getLog(ClientMetadataAdminEndpoints.class); + + @RequestMapping(value = "/oauth/clients/{client}/meta", method = RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public ClientMetadata retrieveClientMetadata(@PathVariable("client") String clientId) { + try { + return clientMetadataProvisioning.retrieve(clientId); + } catch (EmptyResultDataAccessException erdae) { + throw new ClientMetadataException("No client metadata found for " + clientId, HttpStatus.NOT_FOUND); + } + } + + @RequestMapping(value = "/oauth/clients/meta", method = RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public List retrieveAllClientMetadata() { + return clientMetadataProvisioning.retrieveAll(); + } + + @RequestMapping(value = "/oauth/clients/{client}/meta", method = RequestMethod.PUT) + @ResponseStatus(HttpStatus.OK) + public ClientMetadata updateClientMetadata(@RequestBody ClientMetadata clientMetadata, + @PathVariable("client") String clientId) { + + if (StringUtils.hasText(clientMetadata.getClientId())) { + if (!clientId.equals(clientMetadata.getClientId())) { + throw new ClientMetadataException("Client ID in body {" + clientMetadata.getClientId() + "} does not match URL path {" + clientId + "}", HttpStatus.BAD_REQUEST); + } + } else { + clientMetadata.setClientId(clientId); + } + try { + return clientMetadataProvisioning.update(clientMetadata); + } catch (EmptyResultDataAccessException e) { + throw new ClientMetadataException("No client with ID " + clientMetadata.getClientId(), HttpStatus.NOT_FOUND); + } + } + + @ExceptionHandler + public View handleException(ClientMetadataException cme, HttpServletRequest request) { + logger.error("Unhandled exception in client metadata admin endpoints.", cme); + + boolean trace = request.getParameter("trace") != null && !request.getParameter("trace").equals("false"); + return new ConvertingExceptionView(new ResponseEntity<>(new ExceptionReport(cme, trace, cme.getExtraInfo()), + cme.getStatus()), messageConverters); + } + + public void setClientMetadataProvisioning(ClientMetadataProvisioning clientMetadataProvisioning) { + this.clientMetadataProvisioning = clientMetadataProvisioning; + } + + public void setMessageConverters(HttpMessageConverter[] messageConverters) { + this.messageConverters = messageConverters; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataException.java new file mode 100644 index 00000000000..a4db9af491f --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataException.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +package org.cloudfoundry.identity.uaa.client; + +import org.cloudfoundry.identity.uaa.error.UaaException; +import org.springframework.http.HttpStatus; + +import java.util.Map; + +public class ClientMetadataException extends UaaException { + + private final HttpStatus status; + protected Map extraInfo; + + public ClientMetadataException(String message, Throwable cause, HttpStatus status) { + super(message, cause); + this.status = status; + } + + public ClientMetadataException(String message, HttpStatus status) { + super(message); + this.status = status; + } + + public ClientMetadataException(String message, HttpStatus status, Map extraInformation) { + super(message); + this.status = status; + this.extraInfo = extraInformation; + } + + public HttpStatus getStatus() { + return status; + } + + public Map getExtraInfo() { + return extraInfo; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataProvisioning.java new file mode 100644 index 00000000000..834e5901f9a --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientMetadataProvisioning.java @@ -0,0 +1,25 @@ +package org.cloudfoundry.identity.uaa.client; + +import java.util.List; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +public interface ClientMetadataProvisioning { + + List retrieveAll(); + + ClientMetadata retrieve(String id); + + ClientMetadata update(ClientMetadata resource); + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioning.java new file mode 100644 index 00000000000..2ff08a7a5da --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioning.java @@ -0,0 +1,113 @@ +package org.cloudfoundry.identity.uaa.client; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; + +import java.io.ByteArrayInputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +public class JdbcClientMetadataProvisioning implements ClientMetadataProvisioning { + + private static final Log logger = LogFactory.getLog(JdbcClientMetadataProvisioning.class); + + private static final String CLIENT_METADATA_FIELDS = "client_id, identity_zone_id, show_on_home_page, app_launch_url, app_icon"; + private static final String CLIENT_METADATA_QUERY = "select " + CLIENT_METADATA_FIELDS + " from oauth_client_details where client_id=? and identity_zone_id=?"; + private static final String CLIENT_METADATAS_QUERY = "select " + CLIENT_METADATA_FIELDS + " from oauth_client_details where identity_zone_id=? and ((app_launch_url is not null and char_length(app_launch_url)>0) or (app_icon is not null and octet_length(app_icon)>0))"; + private static final String CLIENT_METADATA_UPDATE_FIELDS = "show_on_home_page, app_launch_url, app_icon"; + private static final String CLIENT_METADATA_UPDATE = "update oauth_client_details set " + CLIENT_METADATA_UPDATE_FIELDS.replace(",", "=?,") + "=?" + " where client_id=? and identity_zone_id=?"; + + private JdbcTemplate template; + private final RowMapper mapper = new ClientMetadataRowMapper(); + + JdbcClientMetadataProvisioning(JdbcTemplate template) { + Assert.notNull(template); + this.template = template; + } + + public void setTemplate(JdbcTemplate template) { + this.template = template; + } + + @Override + public List retrieveAll() { + logger.debug("Retrieving UI details for all client"); + return template.query(CLIENT_METADATAS_QUERY, mapper, IdentityZoneHolder.get().getId()); + } + + @Override + public ClientMetadata retrieve(String clientId) { + logger.debug("Retrieving UI details for client: " + clientId); + return template.queryForObject(CLIENT_METADATA_QUERY, mapper, clientId, IdentityZoneHolder.get().getId()); + } + + @Override + public ClientMetadata update(ClientMetadata resource) { + logger.debug("Updating metadata for client: " + resource.getClientId()); + int updated = template.update(CLIENT_METADATA_UPDATE, ps -> { + int pos = 1; + ps.setBoolean(pos++, resource.isShowOnHomePage()); + URL appLaunchUrl = resource.getAppLaunchUrl(); + ps.setString(pos++, appLaunchUrl == null ? null : appLaunchUrl.toString()); + String appIcon = resource.getAppIcon(); + if (appIcon != null) { + byte[] decodedAppIcon = Base64Utils.decode(appIcon.getBytes()); + ps.setBinaryStream(pos++, new ByteArrayInputStream(decodedAppIcon), decodedAppIcon.length); + } else { + ps.setBinaryStream(pos++, new ByteArrayInputStream(new byte[]{}), 0); + } + ps.setString(pos++, resource.getClientId()); + String zone = IdentityZoneHolder.get().getId(); + ps.setString(pos++, zone); + }); + + ClientMetadata resultingClientMetadata = retrieve(resource.getClientId()); + + if (updated > 1) { throw new IncorrectResultSizeDataAccessException(1); } + + return resultingClientMetadata; + } + + + private class ClientMetadataRowMapper implements RowMapper { + + @Override + public ClientMetadata mapRow(ResultSet rs, int rowNum) throws SQLException { + ClientMetadata clientMetadata = new ClientMetadata(); + int pos = 1; + clientMetadata.setClientId(rs.getString(pos++)); + clientMetadata.setIdentityZoneId(rs.getString(pos++)); + clientMetadata.setShowOnHomePage(rs.getBoolean(pos++)); + try { + clientMetadata.setAppLaunchUrl(new URL(rs.getString(pos++))); + } catch (MalformedURLException mue) { + // it is safe to ignore this as client_metadata rows are always created from a ClientMetadata instance whose launch url property is strongly typed to URL + } + byte[] iconBytes = rs.getBytes(pos++); + if(iconBytes != null) { clientMetadata.setAppIcon(new String(Base64Utils.encode(iconBytes))); } + return clientMetadata; + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcQueryableClientDetailsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcQueryableClientDetailsService.java index e0d6ff4c7e5..602eccac795 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcQueryableClientDetailsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcQueryableClientDetailsService.java @@ -12,11 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.client; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; @@ -26,11 +21,16 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.util.StringUtils; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + public class JdbcQueryableClientDetailsService extends AbstractQueryable implements QueryableResourceManager { @@ -63,11 +63,13 @@ protected String getTableName() { @Override public List query(String filter, String sortBy, boolean ascending) { - if (StringUtils.hasText(filter)) { - filter += " and"; + //validate syntax + getQueryConverter().convert(filter, sortBy, ascending); + if (StringUtils.hasText(filter)) { + filter = "(" + filter + ") and "; } filter += " identity_zone_id eq \""+IdentityZoneHolder.get().getId()+"\""; - return super.query(filter, sortBy, ascending); + return super.query(filter, sortBy, ascending); } @Override diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java index 48ce977cc69..a6ecde4f490 100755 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java @@ -12,40 +12,42 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth; -import java.security.Principal; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.scim.ScimGroup; +import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.stereotype.Controller; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.WebRequest; +import javax.servlet.http.HttpServletRequest; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + /** * Controller for retrieving the model for and displaying the confirmation page * for access to a protected resource. @@ -66,6 +68,8 @@ public class AccessController { private ApprovalStore approvalStore = null; + private ScimGroupProvisioning groupProvisioning; + /** * Explicitly requests caller to point back to an authorization endpoint on * "https", even if the incoming request is @@ -87,6 +91,15 @@ public void setApprovalStore(ApprovalStore approvalStore) { this.approvalStore = approvalStore; } + public ScimGroupProvisioning getGroupProvisioning() { + return groupProvisioning; + } + + public AccessController setGroupProvisioning(ScimGroupProvisioning groupProvisioning) { + this.groupProvisioning = groupProvisioning; + return this; + } + @RequestMapping("/oauth/confirm_access") public String confirm(Map model, final HttpServletRequest request, Principal principal, SessionStatus sessionStatus) throws Exception { @@ -110,8 +123,6 @@ public String confirm(Map model, final HttpServletRequest reques BaseClientDetails modifiableClient = new BaseClientDetails(client); modifiableClient.setClientSecret(null); model.put("auth_request", clientAuthRequest); - model.put("client", modifiableClient); // TODO: remove this once it - // has gone from jsp pages model.put("redirect_uri", getRedirectUri(modifiableClient, clientAuthRequest)); Map additionalInfo = client.getAdditionalInformation(); @@ -166,29 +177,19 @@ else if (autoApproved instanceof Boolean && (Boolean) autoApproved || "true".equ } } - model.put("approved_scopes", getScopes(modifiableClient, approvedScopes)); - model.put("denied_scopes", getScopes(modifiableClient, deniedScopes)); - model.put("undecided_scopes", getScopes(modifiableClient, undecidedScopes)); + List> approvedScopeDetails = getScopes(approvedScopes); + model.put("approved_scopes", approvedScopeDetails); + List> undecidedScopeDetails = getScopes(undecidedScopes); + model.put("undecided_scopes", undecidedScopeDetails); + List> deniedScopeDetails = getScopes(deniedScopes); + model.put("denied_scopes", deniedScopeDetails); - // For backward compatibility with older login servers - List> combinedScopes = new ArrayList>(); - if (model.get("approved_scopes") != null) { - @SuppressWarnings("unchecked") - List> scopes = (List>) model.get("approved_scopes"); - combinedScopes.addAll(scopes); - } - if (model.get("denied_scopes") != null) { - @SuppressWarnings("unchecked") - List> scopes = (List>) model.get("denied_scopes"); - combinedScopes.addAll(scopes); - } - if (model.get("undecided_scopes") != null) { - @SuppressWarnings("unchecked") - List> scopes = (List>) model.get("undecided_scopes"); - combinedScopes.addAll(scopes); - } + List> allScopes = new ArrayList<>(); + allScopes.addAll(approvedScopeDetails); + allScopes.addAll(undecidedScopeDetails); + allScopes.addAll(deniedScopeDetails); - model.put("scopes", combinedScopes); + model.put("scopes", allScopes); model.put("message", "To confirm or deny access POST to the following locations with the parameters requested."); @@ -221,48 +222,48 @@ else if (autoApproved instanceof Boolean && (Boolean) autoApproved || "true".equ } - private List> getScopes(ClientDetails client, ArrayList scopes) { + private List> getScopes(ArrayList scopes) { List> result = new ArrayList>(); for (String scope : scopes) { - if (!scope.contains(".")) { - HashMap map = new HashMap(); - map.put("code", SCOPE_PREFIX + scope); - map.put("text", "Access your data with scope '" + scope + "'"); - result.add(map); - } - else { - HashMap map = new HashMap(); - String value = SCOPE_PREFIX + scope; - String resource = scope.substring(0, scope.lastIndexOf(".")); - if (OriginKeys.UAA.equals(resource)) { - // special case: don't need to prompt for internal uaa - // scopes - continue; + + + String[] tokens = scope.split("\\."); + String resource = tokens[0]; + + if(OriginKeys.UAA.equals(resource)) { continue; } + + HashMap map = new HashMap(); + String code = SCOPE_PREFIX + scope; + map.put("code", code); + + Optional group = groupProvisioning.query(String.format("displayName eq '%s'", scope)).stream().findFirst(); + group.ifPresent(g -> { + String description = g.getDescription(); + if (StringUtils.hasText(description)) { + map.put("text", description); } - String access = scope.substring(scope.lastIndexOf(".") + 1); - map.put("code", value); - map.put("text", "Access your '" + resource + "' resources with scope '" + access + "'"); - result.add(map); - } + }); + map.putIfAbsent("text", scope); + + result.add(map); } - Collections.sort(result, new Comparator>() { - @Override - public int compare(Map o1, Map o2) { - String code1 = o1.get("code"); - String code2 = o2.get("code"); - if (code1.startsWith(SCOPE_PREFIX + "password") || code1.startsWith(SCOPE_PREFIX + "openid")) { - code1 = "aaa" + code1; - } - if (code2.startsWith(SCOPE_PREFIX + "password") || code2.startsWith(SCOPE_PREFIX + "openid")) { - code2 = "aaa" + code2; - } - return code1.compareTo(code2); + Collections.sort(result, (map1, map2) -> { + String code1 = map1.get("code"); + String code2 = map2.get("code"); + int i; + if (0 != (i = codeIsPasswordOrOpenId(code2) - codeIsPasswordOrOpenId(code1))) { + return i; } + return code1.compareTo(code2); }); return result; } + private int codeIsPasswordOrOpenId(String code) { + return code.startsWith(SCOPE_PREFIX + "password") || code.startsWith(SCOPE_PREFIX + "openid") ? 1 : 0; + } + private String getRedirectUri(ClientDetails client, AuthorizationRequest clientAuth) { String result = null; if (clientAuth.getRedirectUri() != null) { @@ -311,5 +312,4 @@ private String getPath(HttpServletRequest request, String path) { protected String extractScheme(HttpServletRequest request) { return useSsl != null && useSsl ? "https" : request.getScheme(); } - } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java index 65723550c59..bc6e79f6246 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java @@ -25,6 +25,7 @@ import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; @@ -33,6 +34,8 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.http.HttpStatus.OK; + @Controller public class TokenRevocationEndpoint { @@ -48,24 +51,26 @@ public TokenRevocationEndpoint(MultitenantJdbcClientDetailsService clientDetails } @RequestMapping("/oauth/token/revoke/user/{userId}") - public void revokeTokensForUser(@PathVariable String userId) { + public ResponseEntity revokeTokensForUser(@PathVariable String userId) { logger.debug("Revoking tokens for user: "+userId); ScimUser user = userProvisioning.retrieve(userId); user.setSalt(generator.generate()); userProvisioning.update(userId, user); logger.debug("Tokens revoked for user: "+userId); + return new ResponseEntity<>(OK); } - @RequestMapping("/oauth/token/revoke/user/{clientId}") - public void revokeTokensForClient(@PathVariable String clientId) { + @RequestMapping("/oauth/token/revoke/client/{clientId}") + public ResponseEntity revokeTokensForClient(@PathVariable String clientId) { logger.debug("Revoking tokens for client: " + clientId); BaseClientDetails client = (BaseClientDetails)clientDetailsService.loadClientByClientId(clientId); client.addAdditionalInformation(ClientConstants.TOKEN_SALT,generator.generate()); clientDetailsService.updateClientDetails(client); logger.debug("Tokens revoked for client: " + clientId); + return new ResponseEntity<>(OK); } - @ExceptionHandler(ScimResourceNotFoundException.class) + @ExceptionHandler({ScimResourceNotFoundException.class, NoSuchClientException.class}) public ResponseEntity handleException(Exception e) throws Exception { logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); InvalidTokenException e404 = new InvalidTokenException("Resource not found") { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServices.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServices.java index 7d8da8bdd61..c8eccc4ace7 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServices.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServices.java @@ -55,6 +55,7 @@ import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.common.exceptions.UnauthorizedClientException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetails; @@ -1035,7 +1036,13 @@ private Map getClaimsForToken(String token) { String clientId = (String) claims.get(CID); String userId = (String) claims.get(USER_ID); UaaUser user = null; - ClientDetails client = clientDetailsService.loadClientByClientId(clientId); + ClientDetails client; + try { + client = clientDetailsService.loadClientByClientId(clientId); + } catch (NoSuchClientException x) { + //happens if the client is deleted and token exist + throw new UnauthorizedClientException("Invalid client ID "+clientId); + } try { user = userDatabase.retrieveUserById(userId); } catch (UsernameNotFoundException x) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java index 2031879846c..43ad213a76e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java @@ -47,6 +47,8 @@ import java.util.List; import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CONFLICT; +import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.EXPECTATION_FAILED; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.OK; @@ -97,8 +99,15 @@ public ResponseEntity createIdentityProvider(@RequestBody Iden samlConfigurator.addSamlIdentityProviderDefinition(definition); body.setConfig(definition); } - IdentityProvider createdIdp = identityProviderProvisioning.create(body); - return new ResponseEntity<>(createdIdp, HttpStatus.CREATED); + try { + IdentityProvider createdIdp = identityProviderProvisioning.create(body); + return new ResponseEntity<>(createdIdp, CREATED); + } catch (IdpAlreadyExistsException e) { + return new ResponseEntity<>(body, CONFLICT); + } catch (Exception x) { + logger.debug("Unable to create IdentityProvider["+x.getMessage()+"].", x); + return new ResponseEntity<>(body, INTERNAL_SERVER_ERROR); + } } @RequestMapping(value = "{id}", method = DELETE) @@ -187,7 +196,7 @@ public ResponseEntity testIdentityProvider(@RequestBody IdentityProvider @ExceptionHandler(MetadataProviderException.class) public ResponseEntity handleMetadataProviderException(MetadataProviderException e) { if (e.getMessage().contains("Duplicate")) { - return new ResponseEntity<>(e.getMessage(), HttpStatus.CONFLICT); + return new ResponseEntity<>(e.getMessage(), CONFLICT); } else { return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java index 60d4144ba23..e8914912bf6 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java @@ -18,6 +18,7 @@ import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.ObjectUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; import org.springframework.jdbc.core.JdbcTemplate; @@ -48,13 +49,13 @@ public class JdbcIdentityProviderProvisioning implements IdentityProviderProvisi public static final String ID_PROVIDER_UPDATE_FIELDS = "version,lastmodified,name,type,config,active".replace(",","=?,")+"=?"; - public static final String UPDATE_IDENTITY_PROVIDER_SQL = "update identity_provider set " + ID_PROVIDER_UPDATE_FIELDS + " where id=?"; + public static final String UPDATE_IDENTITY_PROVIDER_SQL = "update identity_provider set " + ID_PROVIDER_UPDATE_FIELDS + " where id=? and identity_zone_id=?"; public static final String DELETE_IDENTITY_PROVIDER_BY_ORIGIN_SQL = "delete from identity_provider where identity_zone_id=? and origin_key = ?"; public static final String DELETE_IDENTITY_PROVIDER_BY_ZONE_SQL = "delete from identity_provider where identity_zone_id=?"; - public static final String IDENTITY_PROVIDER_BY_ID_QUERY = "select " + ID_PROVIDER_FIELDS + " from identity_provider " + "where id=?"; + public static final String IDENTITY_PROVIDER_BY_ID_QUERY = "select " + ID_PROVIDER_FIELDS + " from identity_provider " + "where id=? and identity_zone_id=?"; public static final String IDENTITY_PROVIDER_BY_ORIGIN_QUERY = "select " + ID_PROVIDER_FIELDS + " from identity_provider " + "where origin_key=? and identity_zone_id=? "; @@ -69,7 +70,7 @@ public JdbcIdentityProviderProvisioning(JdbcTemplate jdbcTemplate) { @Override public IdentityProvider retrieve(String id) { - IdentityProvider identityProvider = jdbcTemplate.queryForObject(IDENTITY_PROVIDER_BY_ID_QUERY, mapper, id); + IdentityProvider identityProvider = jdbcTemplate.queryForObject(IDENTITY_PROVIDER_BY_ID_QUERY, mapper, id, IdentityZoneHolder.get().getId()); return identityProvider; } @@ -123,6 +124,7 @@ public void setValues(PreparedStatement ps) throws SQLException { @Override public IdentityProvider update(final IdentityProvider identityProvider) { validate(identityProvider); + final String zoneId = IdentityZoneHolder.get().getId(); jdbcTemplate.update(UPDATE_IDENTITY_PROVIDER_SQL, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement ps) throws SQLException { @@ -134,6 +136,7 @@ public void setValues(PreparedStatement ps) throws SQLException { ps.setString(pos++, JsonUtils.writeValueAsString(identityProvider.getConfig())); ps.setBoolean(pos++, identityProvider.isActive()); ps.setString(pos++, identityProvider.getId().trim()); + ps.setString(pos++, zoneId); } }); return retrieve(identityProvider.getId()); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java index a62ccdd6ac2..a280f339c53 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java @@ -26,7 +26,6 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.security.saml.metadata.ExtendedMetadata; import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; -import org.springframework.util.StringUtils; import java.net.URI; import java.net.URISyntaxException; @@ -44,8 +43,10 @@ import java.util.TimerTask; import static org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR; +import static org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition.PROVIDER_DESCRIPTION; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EXTERNAL_GROUPS_WHITELIST; +import static org.springframework.util.StringUtils.hasText; public class SamlIdentityProviderConfigurator implements InitializingBean { private static Log logger = LogFactory.getLog(SamlIdentityProviderConfigurator.class); @@ -154,10 +155,10 @@ public synchronized ExtendedMetadataDelegate[] addSamlIdentityProviderDefinition if (providerDefinition==null) { throw new NullPointerException(); } - if (!StringUtils.hasText(providerDefinition.getIdpEntityAlias())) { + if (!hasText(providerDefinition.getIdpEntityAlias())) { throw new NullPointerException("SAML IDP Alias must be set"); } - if (!StringUtils.hasText(providerDefinition.getZoneId())) { + if (!hasText(providerDefinition.getZoneId())) { throw new NullPointerException("IDP Zone Id must be set"); } for (SamlIdentityProviderDefinition def : getIdentityProviderDefinitions()) { @@ -305,11 +306,15 @@ public void setIdentityProviders(Map> providers) { String linkText = (String)((Map)entry.getValue()).get("linkText"); String iconUrl = (String)((Map)entry.getValue()).get("iconUrl"); String zoneId = (String)((Map)entry.getValue()).get("zoneId"); + String providerDescription = (String)((Map)entry.getValue()).get(PROVIDER_DESCRIPTION); Boolean addShadowUserOnLogin = (Boolean)((Map)entry.getValue()).get("addShadowUserOnLogin"); List emailDomain = (List) saml.get(EMAIL_DOMAIN_ATTR); List externalGroupsWhitelist = (List) saml.get(EXTERNAL_GROUPS_WHITELIST); Map attributeMappings = (Map) saml.get(ATTRIBUTE_MAPPINGS); SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); + if (hasText(providerDescription)) { + def.setProviderDescription(providerDescription); + } if (alias==null) { throw new IllegalArgumentException("Invalid IDP - alias must not be null ["+metaDataLocation+"]"); } @@ -328,7 +333,7 @@ public void setIdentityProviders(Map> providers) { def.setEmailDomain(emailDomain); def.setExternalGroupsWhitelist(externalGroupsWhitelist); def.setAttributeMappings(attributeMappings); - def.setZoneId(StringUtils.hasText(zoneId) ? zoneId : IdentityZone.getUaa().getId()); + def.setZoneId(hasText(zoneId) ? zoneId : IdentityZone.getUaa().getId()); def.setAddShadowUserOnLogin(addShadowUserOnLogin==null?true:addShadowUserOnLogin); toBeFetchedProviders.add(def); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java index bcfde4fe257..4e21447db1d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,8 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.resources.jdbc; -import java.util.List; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.resources.Queryable; @@ -22,6 +20,8 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import java.util.List; + public abstract class AbstractQueryable implements Queryable { private NamedParameterJdbcTemplate jdbcTemplate; @@ -51,7 +51,7 @@ public void setQueryConverter(SearchQueryConverter queryConverter) { * The maximum number of items fetched from the database in one hit. If less * than or equal to zero, then there is no * limit. - * + * * @param pageSize the page size to use for backing queries (default 200) */ public void setPageSize(int pageSize) { @@ -102,9 +102,14 @@ public List query(String filter, String sortBy, boolean ascending) { } protected String getQuerySQL(String filter, SearchQueryConverter.ProcessedFilter where) { - return filter == null || filter.trim().length()==0 ? - getBaseSqlQuery() : - getBaseSqlQuery() + " where " + where.getSql(); + if (filter == null || filter.trim().length()==0) { + return getBaseSqlQuery(); + } + if (where.hasOrderBy()) { + return getBaseSqlQuery() + " where (" + where.getSql().replace(where.ORDER_BY, ")"+where.ORDER_BY); + } else { + return getBaseSqlQuery() + " where (" + where.getSql() + ")"; + } } protected abstract String getBaseSqlQuery(); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SearchQueryConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SearchQueryConverter.java index 98a70ab4d9f..26b56c6a58b 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SearchQueryConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SearchQueryConverter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,15 +12,18 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.resources.jdbc; -import java.util.Map; - import org.cloudfoundry.identity.uaa.resources.AttributeNameMapper; +import java.util.Map; + public interface SearchQueryConverter { - public static final class ProcessedFilter { + final class ProcessedFilter { + public static final String ORDER_BY_NO_SPACE = "ORDER BY"; + public static final String ORDER_BY = " "+ORDER_BY_NO_SPACE+" "; private final String sql; private final Map params; + private final boolean hasOrderBy; public String getParamPrefix() { return paramPrefix; @@ -36,13 +39,18 @@ public String getSql() { return sql; } + public boolean hasOrderBy() { + return hasOrderBy; + } + public Map getParams() { return params; } - public ProcessedFilter(String sql, Map params) { + public ProcessedFilter(String sql, Map params, boolean hasOrderBy) { this.sql = sql; this.params = params; + this.hasOrderBy = hasOrderBy; } @Override diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java index 7d98b10b89e..8d6ad751105 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java @@ -13,13 +13,6 @@ package org.cloudfoundry.identity.uaa.resources.jdbc; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - import com.unboundid.scim.sdk.SCIMException; import com.unboundid.scim.sdk.SCIMFilter; import org.apache.commons.logging.Log; @@ -29,6 +22,15 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.StringUtils; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter.ProcessedFilter.ORDER_BY; + public class SimpleSearchQueryConverter implements SearchQueryConverter { private static Log logger = LogFactory.getLog(SimpleSearchQueryConverter.class); @@ -56,9 +58,9 @@ public ProcessedFilter convert(String filter, String sortBy, boolean ascending) @Override public ProcessedFilter convert(String filter, String sortBy, boolean ascending, AttributeNameMapper mapper) { String paramPrefix = generateParameterPrefix(filter); - Map values = new HashMap(); + Map values = new HashMap<>(); String where = StringUtils.hasText(filter) ? getWhereClause(filter, sortBy, ascending, values, mapper, paramPrefix) : null; - ProcessedFilter pf = new ProcessedFilter(where, values); + ProcessedFilter pf = new ProcessedFilter(where, values, StringUtils.hasText(sortBy)); pf.setParamPrefix(paramPrefix); return pf; } @@ -81,7 +83,7 @@ private String getWhereClause(String filter, String sortBy, boolean ascending, M sortBy = mapper.mapToInternal(sortBy); // Need to add "asc" or "desc" explicitly to ensure that the pattern // splitting below works - whereClause += " ORDER BY " + sortBy + (ascending ? " ASC" : " DESC"); + whereClause += ORDER_BY + sortBy + (ascending ? " ASC" : " DESC"); } return whereClause; } catch (SCIMException e) { @@ -137,6 +139,14 @@ protected String comparisonClause(SCIMFilter filter, String comparator, Map groups; + private Map groups; private Map> groupMembers; @@ -49,30 +53,68 @@ public class ScimGroupBootstrap implements InitializingBean { private final ScimUserProvisioning scimUserProvisioning; + private Map defaultUserGroups = Collections.EMPTY_MAP; + private Map configuredGroups = Collections.EMPTY_MAP; + private static final String USER_BY_NAME_FILTER = "username eq \"%s\""; private static final String GROUP_BY_NAME_FILTER = "displayName eq \"%s\""; private final Log logger = LogFactory.getLog(getClass()); + private PropertySource messageSource; + private String messagePropertyNameTemplate = "scope.%s"; + + public String getMessagePropertyNameTemplate() { + return messagePropertyNameTemplate; + } + + public void setMessagePropertyNameTemplate(String messagePropertyNameTemplate) { + this.messagePropertyNameTemplate = messagePropertyNameTemplate; + } + public ScimGroupBootstrap(ScimGroupProvisioning scimGroupProvisioning, ScimUserProvisioning scimUserProvisioning, ScimGroupMembershipManager membershipManager) { this.scimGroupProvisioning = scimGroupProvisioning; this.scimUserProvisioning = scimUserProvisioning; this.membershipManager = membershipManager; - groups = new HashSet(); - groupMembers = new HashMap>(); - groupAdmins = new HashMap>(); + groups = new HashMap<>(); + groupMembers = new HashMap<>(); + groupAdmins = new HashMap<>(); + } + + public PropertySource getMessageSource() { + if(messageSource == null) { + String messagesFilename = "messages.properties"; + try { + messageSource = new ResourcePropertySource(messagesFilename); + } catch(IOException ex) { + messageSource = new PropertySource.StubPropertySource(messagesFilename); + } + } + + return messageSource; + } + + public void setMessageSource(PropertySource messageSource) { + this.messageSource = messageSource; } /** * Specify the list of groups to create as a comma-separated list of * group-names * - * @param groups + * @param commaSeparatedGroups */ - public void setGroups(String groups) { - this.groups = StringUtils.commaDelimitedListToSet(groups); + public void setGroups(String commaSeparatedGroups) { + this.configuredGroups = StringUtils.commaDelimitedListToSet(commaSeparatedGroups).stream() + .map(g -> g.split("\\|")) + .collect(new MapCollector<>( + gd -> StringUtils.trimWhitespace(gd[0]), + gd -> gd.length > 1 ? StringUtils.trimWhitespace(gd[1]) : null) + ); + + setCombinedGroups(); } /** @@ -92,7 +134,7 @@ public void setGroupMembers(List membershipInfo) { } Set users = StringUtils.commaDelimitedListToSet(fields[1]); String groupName = fields[0]; - groups.add(groupName); + groups.putIfAbsent(groupName, null); boolean groupAdmin = (3 <= fields.length && "write".equalsIgnoreCase(fields[2])) ? true : false; if (groupAdmin) { @@ -107,28 +149,32 @@ public void setGroupMembers(List membershipInfo) { @Override public void afterPropertiesSet() throws Exception { - for (String g : groups) { - addGroup(g); + List groupInfos = groups.keySet().stream().filter(n -> StringUtils.hasText(n)).map(n -> getOrCreateGroup(n)).collect(Collectors.toList()); + for (int i = 0; i < groupInfos.size(); i++) { + ScimGroup g = groupInfos.get(i); + String description = groups.get(g.getDisplayName()); + if (StringUtils.hasText(description)) { + g.setDescription(description); + groupInfos.set(i, scimGroupProvisioning.update(g.getId(), g)); + } } - for (String g : groups) { + + for (ScimGroup g : groupInfos) { addMembers(g); } } - private void addMembers(String g) { - ScimGroup group = getGroup(g); - if (group == null) { - addGroup(g); - } - List members = getMembers(groupMembers.get(g), ScimGroupMember.GROUP_MEMBER); - members.addAll(getMembers(groupAdmins.get(g), ScimGroupMember.GROUP_ADMIN)); - logger.debug("adding members: " + members + " into group: " + g); + private void addMembers(ScimGroup group) { + String name = group.getDisplayName(); + List members = getMembers(groupMembers.get(name), ScimGroupMember.GROUP_MEMBER); + members.addAll(getMembers(groupAdmins.get(name), ScimGroupMember.GROUP_ADMIN)); + logger.debug("adding members: " + members + " into group: " + name); for (ScimGroupMember member : members) { try { membershipManager.addMember(group.getId(), member); } catch (MemberAlreadyExistsException ex) { - logger.debug(member.getMemberId() + " already is member of group " + g); + logger.debug(member.getMemberId() + " already is member of group " + name); } } } @@ -138,7 +184,7 @@ private List getMembers(Set names, List emptyList(); } - List members = new ArrayList(); + List members = new ArrayList<>(); for (String name : names) { ScimCore member = getScimResourceId(name); if (member != null) { @@ -184,16 +230,39 @@ ScimGroup getGroup(String name) { return null; } - private void addGroup(String name) { - if (name.isEmpty()) { - return; - } + private ScimGroup getOrCreateGroup(String name) { logger.debug("adding group: " + name); ScimGroup g = new ScimGroup(null,name,IdentityZoneHolder.get().getId()); try { - scimGroupProvisioning.create(g); + g = scimGroupProvisioning.create(g); } catch (ScimResourceAlreadyExistsException ex) { - logger.debug("group " + g + " already exists, ignoring..."); + logger.debug("group " + g + " already exists, retrieving..."); + g = getGroup(name); } + return g; + } + + public Set getDefaultUserGroups() { + return defaultUserGroups.keySet(); + } + + public void setDefaultUserGroups(Set defaultUserGroups) { + this.defaultUserGroups = defaultUserGroups.stream() + .collect(new MapCollector<>( + g -> g, + g -> (String) getMessageSource().getProperty(String.format(messagePropertyNameTemplate, g)) + )); + + setCombinedGroups(); } + + private void setCombinedGroups() { + this.groups = new HashMap<>(); + this.groups.putAll(this.defaultUserGroups); + + this.configuredGroups.entrySet().stream() + .filter(e -> StringUtils.hasText(e.getValue()) || !groups.containsKey(e.getKey())) + .forEach(e -> groups.put(e.getKey(), e.getValue())); + } + } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java index a4e2d13f09e..769a271e428 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java @@ -14,7 +14,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.event.UnverifiedUserAuthenticationEvent; import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java index 295b7aed403..370b2def74a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java @@ -1,6 +1,6 @@ /******************************************************************************* * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. @@ -56,17 +56,18 @@ public Log getLogger() { return logger; } - public static final String GROUP_FIELDS = "id,displayName,created,lastModified,version,identity_zone_id"; + public static final String GROUP_FIELDS = "id,displayName,description,created,lastModified,version,identity_zone_id"; public static final String GROUP_TABLE = "groups"; public static final String GROUP_MEMBERSHIP_TABLE = "group_membership"; public static final String EXTERNAL_GROUP_TABLE = "external_group_mapping"; - public static final String ADD_GROUP_SQL = String.format("insert into %s ( %s ) values (?,?,?,?,?,?)", GROUP_TABLE, - GROUP_FIELDS); + public static final String ADD_GROUP_SQL = String.format("insert into %s ( %s ) values (?,?,?,?,?,?,?)", + GROUP_TABLE, + GROUP_FIELDS); public static final String UPDATE_GROUP_SQL = String.format( - "update %s set version=?, displayName=?, lastModified=? where id=? and version=?", GROUP_TABLE); + "update %s set version=?, displayName=?, description=?, lastModified=? where id=? and version=? and identity_zone_id=?", GROUP_TABLE); public static final String GET_GROUP_SQL = String.format("select %s from %s where id=? and identity_zone_id=?", GROUP_FIELDS, GROUP_TABLE); @@ -96,8 +97,11 @@ protected String getBaseSqlQuery() { @Override public List query(String filter, String sortBy, boolean ascending) { + //validate syntax + getQueryConverter().convert(filter, sortBy, ascending); + if (StringUtils.hasText(filter)) { - filter += " and"; + filter = "("+ filter+ ") and"; } filter += " identity_zone_id eq \""+IdentityZoneHolder.get().getId()+"\""; return super.query(filter, sortBy, ascending); @@ -130,15 +134,18 @@ public ScimGroup create(final ScimGroup group) throws InvalidScimResourceExcepti logger.debug("creating new group with id: " + id); try { validateGroup(group); + final String zoneId = IdentityZoneHolder.get().getId(); jdbcTemplate.update(ADD_GROUP_SQL, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, id); - ps.setString(2, group.getDisplayName()); - ps.setTimestamp(3, new Timestamp(new Date().getTime())); - ps.setTimestamp(4, new Timestamp(new Date().getTime())); - ps.setInt(5, group.getVersion()); - ps.setString(6, group.getZoneId()); + int pos = 1; + ps.setString(pos++, id); + ps.setString(pos++, group.getDisplayName()); + ps.setString(pos++, group.getDescription()); + ps.setTimestamp(pos++, new Timestamp(new Date().getTime())); + ps.setTimestamp(pos++, new Timestamp(new Date().getTime())); + ps.setInt(pos++, group.getVersion()); + ps.setString(pos++, zoneId); } }); } catch (DuplicateKeyException ex) { @@ -153,14 +160,18 @@ public ScimGroup update(final String id, final ScimGroup group) throws InvalidSc ScimResourceNotFoundException { try { validateGroup(group); + final String zoneId = IdentityZoneHolder.get().getId(); int updated = jdbcTemplate.update(UPDATE_GROUP_SQL, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement ps) throws SQLException { - ps.setInt(1, group.getVersion() + 1); - ps.setString(2, group.getDisplayName()); - ps.setTimestamp(3, new Timestamp(new Date().getTime())); - ps.setString(4, id); - ps.setInt(5, group.getVersion()); + int pos = 1; + ps.setInt(pos++, group.getVersion() + 1); + ps.setString(pos++, group.getDisplayName()); + ps.setString(pos++, group.getDescription()); + ps.setTimestamp(pos++, new Timestamp(new Date().getTime())); + ps.setString(pos++, id); + ps.setInt(pos++, group.getVersion()); + ps.setString(pos++, zoneId); } }); if (updated != 1) { @@ -209,13 +220,16 @@ private static final class ScimGroupRowMapper implements RowMapper { @Override public ScimGroup mapRow(ResultSet rs, int rowNum) throws SQLException { - String id = rs.getString(1); - String name = rs.getString(2); - Date created = rs.getTimestamp(3); - Date modified = rs.getTimestamp(4); - int version = rs.getInt(5); - String zoneId = rs.getString(6); + int pos = 1; + String id = rs.getString(pos++); + String name = rs.getString(pos++); + String description = rs.getString(pos++); + Date created = rs.getTimestamp(pos++); + Date modified = rs.getTimestamp(pos++); + int version = rs.getInt(pos++); + String zoneId = rs.getString(pos++); ScimGroup group = new ScimGroup(id, name, zoneId); + group.setDescription(description); ScimMeta meta = new ScimMeta(created, modified, version); group.setMeta(meta); return group; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java index c5d74ff0bc2..7be2540e300 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java @@ -14,8 +14,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.resources.ResourceMonitor; import org.cloudfoundry.identity.uaa.resources.jdbc.AbstractQueryable; import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; @@ -46,7 +46,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; -import java.sql.Types; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -56,6 +55,8 @@ import java.util.UUID; import java.util.regex.Pattern; +import static java.sql.Types.VARCHAR; + /** * @author Luke Taylor * @author Dave Syer @@ -75,19 +76,19 @@ public Log getLogger() { public static final String CREATE_USER_SQL = "insert into users (" + USER_FIELDS + ",password) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; - public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, userName=?, email=?, givenName=?, familyName=?, active=?, phoneNumber=?, verified=?, origin=?, external_id=?, salt=? where id=? and version=?"; + public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, userName=?, email=?, givenName=?, familyName=?, active=?, phoneNumber=?, verified=?, origin=?, external_id=?, salt=? where id=? and version=? and identity_zone_id=?"; - public static final String DEACTIVATE_USER_SQL = "update users set active=? where id=?"; + public static final String DEACTIVATE_USER_SQL = "update users set active=? where id=? and identity_zone_id=?"; - public static final String VERIFY_USER_SQL = "update users set verified=? where id=?"; + public static final String VERIFY_USER_SQL = "update users set verified=? where id=? and identity_zone_id=?"; - public static final String DELETE_USER_SQL = "delete from users where id=?"; + public static final String DELETE_USER_SQL = "delete from users where id=? and identity_zone_id=?"; - public static final String CHANGE_PASSWORD_SQL = "update users set lastModified=?, password=?, passwd_lastmodified=? where id=?"; + public static final String CHANGE_PASSWORD_SQL = "update users set lastModified=?, password=?, passwd_lastmodified=? where id=? and identity_zone_id=?"; - public static final String READ_PASSWORD_SQL = "select password from users where id=?"; + public static final String READ_PASSWORD_SQL = "select password from users where id=? and identity_zone_id=?"; - public static final String USER_BY_ID_QUERY = "select " + USER_FIELDS + " from users " + "where id=?"; + public static final String USER_BY_ID_QUERY = "select " + USER_FIELDS + " from users " + "where id=? and identity_zone_id=?"; public static final String ALL_USERS = "select " + USER_FIELDS + " from users"; @@ -123,7 +124,7 @@ public JdbcScimUserProvisioning(JdbcTemplate jdbcTemplate, JdbcPagingListFactory @Override public ScimUser retrieve(String id) { try { - ScimUser u = jdbcTemplate.queryForObject(USER_BY_ID_QUERY, mapper, id); + ScimUser u = jdbcTemplate.queryForObject(USER_BY_ID_QUERY, mapper, id, IdentityZoneHolder.get().getId()); return u; } catch (EmptyResultDataAccessException e) { throw new ScimResourceNotFoundException("User " + id + " does not exist"); @@ -147,8 +148,11 @@ public List retrieveAll() { @Override public List query(String filter, String sortBy, boolean ascending) { + //validate syntax + getQueryConverter().convert(filter, sortBy, ascending); + if (StringUtils.hasText(filter)) { - filter += " and"; + filter = "("+ filter+ ") and"; } filter += " identity_zone_id eq \""+IdentityZoneHolder.get().getId()+"\""; return super.query(filter, sortBy, ascending); @@ -241,7 +245,7 @@ public ScimUser update(final String id, final ScimUser user) throws InvalidScimR validate(user); logger.debug("Updating user " + user.getUserName()); final String origin = StringUtils.hasText(user.getOrigin()) ? user.getOrigin() : OriginKeys.UAA; - + final String zoneId = IdentityZoneHolder.get().getId(); int updated = jdbcTemplate.update(UPDATE_USER_SQL, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement ps) throws SQLException { @@ -261,6 +265,7 @@ public void setValues(PreparedStatement ps) throws SQLException { ps.setString(pos++, user.getSalt()); ps.setString(pos++, id); ps.setInt(pos++, user.getVersion()); + ps.setString(pos++, zoneId); } }); ScimUser result = retrieve(id); @@ -285,6 +290,7 @@ public void changePassword(final String id, String oldPassword, final String new return; //we don't want to update the same password } final String encNewPassword = passwordEncoder.encode(newPassword); + final String zoneId = IdentityZoneHolder.get().getId(); int updated = jdbcTemplate.update(CHANGE_PASSWORD_SQL, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement ps) throws SQLException { @@ -293,6 +299,7 @@ public void setValues(PreparedStatement ps) throws SQLException { ps.setString(2, encNewPassword); ps.setTimestamp(3, getPasswordLastModifiedTimestamp(t)); ps.setString(4, id); + ps.setString(5, zoneId); } }); if (updated == 0) { @@ -307,8 +314,13 @@ public void setValues(PreparedStatement ps) throws SQLException { public boolean checkPasswordMatches(String id, String password) { String currentPassword; try { - currentPassword = jdbcTemplate.queryForObject(READ_PASSWORD_SQL, new Object[] { id }, - new int[] { Types.VARCHAR }, String.class); + currentPassword = + jdbcTemplate.queryForObject( + READ_PASSWORD_SQL, + new Object[] { id, IdentityZoneHolder.get().getId() }, + new int[] { VARCHAR, VARCHAR }, + String.class + ); } catch (IncorrectResultSizeDataAccessException e) { throw new ScimResourceNotFoundException("User " + id + " does not exist"); } @@ -327,10 +339,9 @@ private ScimUser deactivateUser(ScimUser user, int version) { int updated; if (version < 0) { // Ignore - updated = jdbcTemplate.update(DEACTIVATE_USER_SQL, false, user.getId()); - } - else { - updated = jdbcTemplate.update(DEACTIVATE_USER_SQL + " and version=?", false, user.getId(), version); + updated = jdbcTemplate.update(DEACTIVATE_USER_SQL, false, user.getId(), IdentityZoneHolder.get().getId()); + } else { + updated = jdbcTemplate.update(DEACTIVATE_USER_SQL + " and version=?", false, user.getId(), IdentityZoneHolder.get().getId(), version); } if (updated == 0) { throw new OptimisticLockingFailureException(String.format( @@ -351,10 +362,10 @@ public ScimUser verifyUser(String id, int version) throws ScimResourceNotFoundEx int updated; if (version < 0) { // Ignore - updated = jdbcTemplate.update(VERIFY_USER_SQL, true, id); + updated = jdbcTemplate.update(VERIFY_USER_SQL, true, id, IdentityZoneHolder.get().getId()); } else { - updated = jdbcTemplate.update(VERIFY_USER_SQL + " and version=?", true, id, version); + updated = jdbcTemplate.update(VERIFY_USER_SQL + " and version=?", true, id, IdentityZoneHolder.get().getId(), version); } ScimUser user = retrieve(id); if (updated == 0) { @@ -373,10 +384,10 @@ private ScimUser deleteUser(ScimUser user, int version) { int updated; if (version < 0) { - updated = jdbcTemplate.update(DELETE_USER_SQL, user.getId()); + updated = jdbcTemplate.update(DELETE_USER_SQL, user.getId(), IdentityZoneHolder.get().getId()); } else { - updated = jdbcTemplate.update(DELETE_USER_SQL + " and version=?", user.getId(), version); + updated = jdbcTemplate.update(DELETE_USER_SQL + " and version=?", user.getId(), IdentityZoneHolder.get().getId(), version); } if (updated == 0) { throw new OptimisticLockingFailureException(String.format( @@ -468,11 +479,11 @@ public ScimUser mapRow(ResultSet rs, int rowNum) throws SQLException { @Override public int getTotalCount() { - Integer count = jdbcTemplate.queryForObject("select count(*) from users",Integer.class); - if (count == null) { - return 0; - } - return count; + Integer count = jdbcTemplate.queryForObject("select count(*) from users",Integer.class); + if (count == null) { + return 0; + } + return count; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java index e24b632ecb6..ae7a4214863 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; + import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -27,10 +27,10 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; /** @@ -46,14 +46,12 @@ public class JdbcUaaUserDatabase implements UaaUserDatabase { + "where lower(username) = ? and active=? and origin=? and identity_zone_id=?"; public static final String DEFAULT_USER_BY_ID_QUERY = "select " + USER_FIELDS + "from users " - + "where id = ? and active=?"; + + "where id = ? and active=? and identity_zone_id=?"; public static final String DEFAULT_USER_BY_EMAIL_AND_ORIGIN_QUERY = "select " + USER_FIELDS + "from users " + "where lower(email)=? and active=? and origin=? and identity_zone_id=?"; - private String userAuthoritiesQuery = null; - - private String userByUserNameQuery = DEFAULT_USER_BY_USERNAME_QUERY; + private String AUTHORITIES_QUERY = "select g.id,g.displayName from groups g, group_membership m where g.id = m.group_id and m.member_id = ? and g.identity_zone_id=?"; private JdbcTemplate jdbcTemplate; @@ -61,14 +59,6 @@ public class JdbcUaaUserDatabase implements UaaUserDatabase { private Set defaultAuthorities = new HashSet(); - public void setUserByUserNameQuery(String userByUserNameQuery) { - this.userByUserNameQuery = userByUserNameQuery; - } - - public void setUserAuthoritiesQuery(String userAuthoritiesQuery) { - this.userAuthoritiesQuery = userAuthoritiesQuery; - } - public void setDefaultAuthorities(Set defaultAuthorities) { this.defaultAuthorities = defaultAuthorities; } @@ -81,7 +71,7 @@ public JdbcUaaUserDatabase(JdbcTemplate jdbcTemplate) { @Override public UaaUser retrieveUserByName(String username, String origin) throws UsernameNotFoundException { try { - return jdbcTemplate.queryForObject(userByUserNameQuery, mapper, username.toLowerCase(Locale.US), true, origin, IdentityZoneHolder.get().getId()); + return jdbcTemplate.queryForObject(DEFAULT_USER_BY_USERNAME_QUERY, mapper, username.toLowerCase(Locale.US), true, origin, IdentityZoneHolder.get().getId()); } catch (EmptyResultDataAccessException e) { throw new UsernameNotFoundException(username); } @@ -90,7 +80,7 @@ public UaaUser retrieveUserByName(String username, String origin) throws Usernam @Override public UaaUser retrieveUserById(String id) throws UsernameNotFoundException { try { - return jdbcTemplate.queryForObject(DEFAULT_USER_BY_ID_QUERY, mapper, id, true); + return jdbcTemplate.queryForObject(DEFAULT_USER_BY_ID_QUERY, mapper, id, true, IdentityZoneHolder.get().getId()); } catch (EmptyResultDataAccessException e) { throw new UsernameNotFoundException(id); } @@ -133,13 +123,9 @@ public UaaUser mapRow(ResultSet rs, int rowNum) throws SQLException { .withLegacyVerificationBehavior(rs.getBoolean(17)) ; - if (userAuthoritiesQuery == null) { - return new UaaUser(prototype); - } else { - List authorities = AuthorityUtils - .commaSeparatedStringToAuthorityList(getAuthorities(id)); - return new UaaUser(prototype.withAuthorities(authorities)); - } + List authorities = + AuthorityUtils.commaSeparatedStringToAuthorityList(getAuthorities(id)); + return new UaaUser(prototype.withAuthorities(authorities)); } private List getDefaultAuthorities(String defaultAuth) { @@ -151,14 +137,26 @@ private List getDefaultAuthorities(String defaultAuth) { } private String getAuthorities(final String userId) { - List authorities; + Set authorities = new HashSet<>(); + getAuthorities(authorities, userId); + authorities.addAll(defaultAuthorities); + return StringUtils.collectionToCommaDelimitedString(new HashSet<>(authorities)); + } + + protected void getAuthorities(Set authorities, final String memberId) { + List> results; try { - authorities = jdbcTemplate.queryForList(userAuthoritiesQuery, String.class, userId); + results = jdbcTemplate.queryForList(AUTHORITIES_QUERY, memberId, IdentityZoneHolder.get().getId()); + for (Map record : results) { + String displayName = (String)record.get("displayName"); + String groupId = (String)record.get("id"); + if (!authorities.contains(displayName)) { + authorities.add(displayName); + getAuthorities(authorities, groupId); + } + } } catch (EmptyResultDataAccessException ex) { - authorities = Collections. emptyList(); } - authorities.addAll(defaultAuthorities); - return StringUtils.collectionToCommaDelimitedString(new HashSet(authorities)); } } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java index d6b53566e98..85a38b21b46 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java @@ -25,6 +25,9 @@ import java.util.List; import java.util.Map; +import static org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition.PROVIDER_DESCRIPTION; +import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP_PREFIX; + public final class LdapUtils { private LdapUtils() {} @@ -83,6 +86,8 @@ public static LdapIdentityProviderDefinition fromConfig(Map ldap return definition; } + + if (ldapConfig.get(LdapIdentityProviderDefinition.LDAP_EMAIL_DOMAIN)!=null) { definition.setEmailDomain((List) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_EMAIL_DOMAIN)); } @@ -155,6 +160,11 @@ public static LdapIdentityProviderDefinition fromConfig(Map ldap definition.addAttributeMapping(entry.getKey().substring(LDAP_ATTR_MAP_PREFIX.length()), entry.getValue()); } } + + if (ldapConfig.get(LDAP_PREFIX+PROVIDER_DESCRIPTION)!=null && ldapConfig.get(LDAP_PREFIX+PROVIDER_DESCRIPTION) instanceof String) { + definition.setProviderDescription((String)ldapConfig.get(LDAP_PREFIX+PROVIDER_DESCRIPTION)); + } + return definition; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/LineAwareLayout.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/LineAwareLayout.java index cd0d83df9a9..fc9fa23c4c4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/LineAwareLayout.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/LineAwareLayout.java @@ -16,6 +16,7 @@ import org.apache.log4j.Layout; import org.apache.log4j.spi.LoggingEvent; +import org.springframework.util.StringUtils; /** * Created by pivotal on 10/28/15. @@ -46,8 +47,10 @@ public String format(LoggingEvent event) { String[] throwable; if(messageLayout == null && (throwable = event.getThrowableStrRep()) != null) { lines = throwable; - } else { + } else if(message != null) { lines = message.split("\r?\n"); + } else { + lines = new String[0]; } StringBuffer strBuf = new StringBuffer(); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/MapCollector.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/MapCollector.java new file mode 100644 index 00000000000..1dca67410ef --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/MapCollector.java @@ -0,0 +1,51 @@ +package org.cloudfoundry.identity.uaa.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +/** +* Created by pivotal on 1/18/16. +*/ +public class MapCollector implements Collector, Map> { + + private final Function keyMapper; + private final Function valueMapper; + + public MapCollector(Function keyMapper, Function valueMapper) { + + this.keyMapper = keyMapper; + this.valueMapper = valueMapper; + } + + @Override + public Supplier> supplier() { + return HashMap::new; + } + + @Override + public BiConsumer, T> accumulator() { + return (m, item) -> m.put(keyMapper.apply(item), valueMapper.apply(item)); + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> { throw new IllegalStateException(String.format("Duplicate key %s", left)); }; + } + + @Override + public Function, Map> finisher() { + return m -> m; + } + + @Override + public Set characteristics() { + return Collections.singleton(Characteristics.UNORDERED); + } +} diff --git a/server/src/main/resources/login-ui.xml b/server/src/main/resources/login-ui.xml index 4ab185b99c0..59b23840095 100644 --- a/server/src/main/resources/login-ui.xml +++ b/server/src/main/resources/login-ui.xml @@ -400,10 +400,6 @@ - - - - @@ -592,6 +588,7 @@ + diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_1__Add_Group_Description.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_1__Add_Group_Description.sql new file mode 100644 index 00000000000..b32ae67a141 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_1__Add_Group_Description.sql @@ -0,0 +1,15 @@ +-- +-- Cloud Foundry +-- Copyright (c) [2016] Pivotal Software, Inc. All Rights Reserved. +-- +-- This product is licensed to you under the Apache License, Version 2.0 (the "License"). +-- You may not use this product except in compliance with the License. +-- +-- This product includes a number of subcomponents with +-- separate copyright notices and license terms. Your use of these +-- subcomponents is subject to the terms and conditions of the +-- subcomponent's license, as noted in the LICENSE file. +-- + +-- add zone id to the groups table +ALTER TABLE groups ADD COLUMN description varchar(255); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_2__Add_Client_Metadata_Columns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_2__Add_Client_Metadata_Columns.sql new file mode 100644 index 00000000000..454d1d40420 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_2__Add_Client_Metadata_Columns.sql @@ -0,0 +1,8 @@ +ALTER TABLE oauth_client_details ADD COLUMN show_on_home_page BOOLEAN DEFAULT TRUE NOT NULL; +ALTER TABLE oauth_client_details ADD COLUMN app_launch_url VARCHAR(1024); +ALTER TABLE oauth_client_details ADD COLUMN app_icon BLOB; + +ALTER TABLE oauth_client_details ALTER COLUMN identity_zone_id SET DATA TYPE VARCHAR(36); -- to avoid whitespace padding + + + diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_1__Add_Group_Description.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_1__Add_Group_Description.sql new file mode 100644 index 00000000000..b32ae67a141 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_1__Add_Group_Description.sql @@ -0,0 +1,15 @@ +-- +-- Cloud Foundry +-- Copyright (c) [2016] Pivotal Software, Inc. All Rights Reserved. +-- +-- This product is licensed to you under the Apache License, Version 2.0 (the "License"). +-- You may not use this product except in compliance with the License. +-- +-- This product includes a number of subcomponents with +-- separate copyright notices and license terms. Your use of these +-- subcomponents is subject to the terms and conditions of the +-- subcomponent's license, as noted in the LICENSE file. +-- + +-- add zone id to the groups table +ALTER TABLE groups ADD COLUMN description varchar(255); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_2__Add_Client_Metadata_Columns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_2__Add_Client_Metadata_Columns.sql new file mode 100644 index 00000000000..3fdc30b2ddf --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_2__Add_Client_Metadata_Columns.sql @@ -0,0 +1,3 @@ +ALTER TABLE oauth_client_details ADD COLUMN show_on_home_page BOOLEAN DEFAULT TRUE NOT NULL; +ALTER TABLE oauth_client_details ADD COLUMN app_launch_url VARCHAR(1024); +ALTER TABLE oauth_client_details ADD COLUMN app_icon MEDIUMBLOB; diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_1__Add_Group_Description.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_1__Add_Group_Description.sql new file mode 100644 index 00000000000..b32ae67a141 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_1__Add_Group_Description.sql @@ -0,0 +1,15 @@ +-- +-- Cloud Foundry +-- Copyright (c) [2016] Pivotal Software, Inc. All Rights Reserved. +-- +-- This product is licensed to you under the Apache License, Version 2.0 (the "License"). +-- You may not use this product except in compliance with the License. +-- +-- This product includes a number of subcomponents with +-- separate copyright notices and license terms. Your use of these +-- subcomponents is subject to the terms and conditions of the +-- subcomponent's license, as noted in the LICENSE file. +-- + +-- add zone id to the groups table +ALTER TABLE groups ADD COLUMN description varchar(255); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_2__Add_Client_Metadata_Columns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_2__Add_Client_Metadata_Columns.sql new file mode 100644 index 00000000000..fe54d23c00e --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_2__Add_Client_Metadata_Columns.sql @@ -0,0 +1,3 @@ +ALTER TABLE oauth_client_details ADD COLUMN show_on_home_page BOOLEAN DEFAULT TRUE NOT NULL; +ALTER TABLE oauth_client_details ADD COLUMN app_launch_url VARCHAR(1024); +ALTER TABLE oauth_client_details ADD COLUMN app_icon BYTEA; diff --git a/server/src/main/resources/templates/web/access_confirmation.html b/server/src/main/resources/templates/web/access_confirmation.html index 6022853707e..9b027dee272 100644 --- a/server/src/main/resources/templates/web/access_confirmation.html +++ b/server/src/main/resources/templates/web/access_confirmation.html @@ -28,7 +28,7 @@

Cloudbees

  • - +
  • @@ -36,7 +36,7 @@

    Cloudbees

  • - +
  • @@ -44,7 +44,7 @@

    Cloudbees

  • - +
  • diff --git a/server/src/main/resources/templates/web/layouts/main.html b/server/src/main/resources/templates/web/layouts/main.html index 14be36df1a3..d4ab50d3af1 100644 --- a/server/src/main/resources/templates/web/layouts/main.html +++ b/server/src/main/resources/templates/web/layouts/main.html @@ -1,35 +1,36 @@ + companyName=('CloudFoundry.org Foundation'), + copyright=(${@environment.getProperty('login.branding.footerLegalText')} ?: ('Copyright © ' + ${companyName} + ', Inc. ' + ${#dates.year(#dates.createNow())}) + '. All Rights Reserved.')"> - [[${pivotal and isUaa ? 'Pivotal' : (isUaa ? 'Cloud Foundry' : zoneName)}]] - + [[${isUaa ? 'Cloud Foundry' : zoneName}]] + - + - +
    -
    +
    diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java index 45fef8abb35..a0101bb744c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java @@ -55,13 +55,16 @@ public class ClientAdminBootstrapTests extends JdbcTestBase { private ClientAdminBootstrap bootstrap; private JdbcClientDetailsService clientRegistrationService; + private ClientMetadataProvisioning clientMetadataProvisioning; @Before public void setUpClientAdminTests() throws Exception { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); bootstrap = new ClientAdminBootstrap(encoder); clientRegistrationService = new MultitenantJdbcClientDetailsService(dataSource); + clientMetadataProvisioning = new JdbcClientMetadataProvisioning(jdbcTemplate); bootstrap.setClientRegistrationService(clientRegistrationService); + bootstrap.setClientMetadataProvisioning(clientMetadataProvisioning); clientRegistrationService.setPasswordEncoder(encoder); } @@ -95,6 +98,23 @@ public void testSimpleAddClientWithSignupSuccessRedirectUrl() throws Exception { assertTrue(clientDetails.getRegisteredRedirectUri().contains("callback_url")); } + @Test + public void clientMetadata_getsBootstrapped() throws Exception { + Map map = new HashMap<>(); + map.put("id", "foo"); + map.put("secret", "bar"); + map.put("show-on-homepage", true); + map.put("app-launch-url", "http://takemetothispage.com"); + map.put("app-icon", "bAsE64encODEd/iMAgE="); + bootstrap.setClients(Collections.singletonMap((String) map.get("id"), map)); + bootstrap.afterPropertiesSet(); + + ClientMetadata clientMetadata = clientMetadataProvisioning.retrieve("foo"); + assertTrue(clientMetadata.isShowOnHomePage()); + assertEquals("http://takemetothispage.com", clientMetadata.getAppLaunchUrl().toString()); + assertEquals("bAsE64encODEd/iMAgE=", clientMetadata.getAppIcon()); + } + @Test public void testAdditionalInformation() throws Exception { List idps = Arrays.asList("idp1", "idp1"); @@ -129,7 +149,10 @@ public void testSimpleAddClientWithChangeEmailRedirectUrl() throws Exception { @Test public void testSimpleAddClientWithAutoApprove() throws Exception { ClientRegistrationService clientRegistrationService = mock(ClientRegistrationService.class); + ClientMetadataProvisioning clientMetadataProvisioning = mock(ClientMetadataProvisioning.class); bootstrap.setClientRegistrationService(clientRegistrationService); + bootstrap.setClientMetadataProvisioning(clientMetadataProvisioning); + Map map = new HashMap<>(); map.put("id", "foo"); map.put("secret", "bar"); @@ -140,8 +163,11 @@ public void testSimpleAddClientWithAutoApprove() throws Exception { "uaa.none"); output.setClientSecret("bar"); bootstrap.setAutoApproveClients(Arrays.asList("foo")); + + when(clientMetadataProvisioning.update(any(ClientMetadata.class))).thenReturn(new ClientMetadata()); when(clientRegistrationService.listClientDetails()).thenReturn(Collections. emptyList()) .thenReturn(Collections. singletonList(output)); + bootstrap.setClients(Collections.singletonMap((String) map.get("id"), map)); bootstrap.afterPropertiesSet(); verify(clientRegistrationService).addClientDetails(output); @@ -153,11 +179,16 @@ public void testSimpleAddClientWithAutoApprove() throws Exception { @Test public void testOverrideClient() throws Exception { ClientRegistrationService clientRegistrationService = mock(ClientRegistrationService.class); + ClientMetadataProvisioning clientMetadataProvisioning = mock(ClientMetadataProvisioning.class); bootstrap.setClientRegistrationService(clientRegistrationService); + bootstrap.setClientMetadataProvisioning(clientMetadataProvisioning); + Map map = new HashMap<>(); map.put("secret", "bar"); map.put("override", true); bootstrap.setClients(Collections.singletonMap("foo", map)); + when(clientMetadataProvisioning.update(any(ClientMetadata.class))).thenReturn(new ClientMetadata()); + doThrow(new ClientAlreadyExistsException("Planned")).when(clientRegistrationService).addClientDetails( any(ClientDetails.class)); bootstrap.afterPropertiesSet(); @@ -169,10 +200,14 @@ public void testOverrideClient() throws Exception { @Test public void testOverrideClientByDefault() throws Exception { ClientRegistrationService clientRegistrationService = mock(ClientRegistrationService.class); + ClientMetadataProvisioning clientMetadataProvisioning = mock(ClientMetadataProvisioning.class); bootstrap.setClientRegistrationService(clientRegistrationService); + bootstrap.setClientMetadataProvisioning(clientMetadataProvisioning); + Map map = new HashMap<>(); map.put("secret", "bar"); bootstrap.setClients(Collections.singletonMap("foo", map)); + when(clientMetadataProvisioning.update(any(ClientMetadata.class))).thenReturn(new ClientMetadata()); doThrow(new ClientAlreadyExistsException("Planned")).when(clientRegistrationService).addClientDetails( any(ClientDetails.class)); bootstrap.afterPropertiesSet(); @@ -185,7 +220,9 @@ public void testOverrideClientByDefault() throws Exception { @SuppressWarnings("unchecked") public void testOverrideClientWithYaml() throws Exception { ClientRegistrationService clientRegistrationService = mock(ClientRegistrationService.class); + ClientMetadataProvisioning clientMetadataProvisioning = mock(ClientMetadataProvisioning.class); bootstrap.setClientRegistrationService(clientRegistrationService); + bootstrap.setClientMetadataProvisioning(clientMetadataProvisioning); @SuppressWarnings("rawtypes") Map fooClient = new Yaml().loadAs("id: foo\noverride: true\nsecret: bar\n" @@ -198,6 +235,7 @@ public void testOverrideClientWithYaml() throws Exception { clients.put("foo", fooClient); clients.put("bar", barClient); bootstrap.setClients(clients); + when(clientMetadataProvisioning.update(any(ClientMetadata.class))).thenReturn(new ClientMetadata()); doThrow(new ClientAlreadyExistsException("Planned")).when(clientRegistrationService).addClientDetails( any(ClientDetails.class)); bootstrap.afterPropertiesSet(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidatorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidatorTests.java new file mode 100644 index 00000000000..afdbb133dcf --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidatorTests.java @@ -0,0 +1,72 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.client; + +import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; +import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; + +import java.util.Arrays; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ClientAdminEndpointsValidatorTests { + + BaseClientDetails client; + BaseClientDetails caller; + ClientAdminEndpointsValidator validator; + + @Before + public void createClient() throws Exception { + client = new BaseClientDetails("newclient","","","client_credentials",""); + client.setClientSecret("secret"); + caller = new BaseClientDetails("caller","","","client_credentials","clients.write"); + validator = new ClientAdminEndpointsValidator(); + } + + @Test + public void testValidate_Should_Allow_Prefix_Names() throws Exception { + QueryableResourceManager clientDetailsService = mock(QueryableResourceManager.class); + SecurityContextAccessor accessor = mock(SecurityContextAccessor.class); + when(accessor.isAdmin()).thenReturn(false); + when(accessor.getScopes()).thenReturn(Arrays.asList("clients.write")); + when(accessor.getClientId()).thenReturn(caller.getClientId()); + when(clientDetailsService.retrieve(eq(caller.getClientId()))).thenReturn(caller); + validator.setClientDetailsService(clientDetailsService); + validator.setSecurityContextAccessor(accessor); + + client.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("uaa.resource"))); + validator.validate(client, true, true); + client.setAuthorities(Arrays.asList(new SimpleGrantedAuthority(caller.getClientId()+".some.other.authority"))); + + try { + validator.validate(client, true, true); + fail(); + } catch (InvalidClientDetailsException x) { + assertTrue(x.getMessage().contains("not an allowed authority")); + } + + + } + +} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioningTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioningTest.java new file mode 100644 index 00000000000..dafaa0975de --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioningTest.java @@ -0,0 +1,115 @@ +package org.cloudfoundry.identity.uaa.client; + +import org.cloudfoundry.identity.uaa.test.JdbcTestBase; +import org.cloudfoundry.identity.uaa.util.PredicateMatcher; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.junit.Before; +import org.junit.Test; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

    + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

    + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +public class JdbcClientMetadataProvisioningTest extends JdbcTestBase { + + JdbcClientMetadataProvisioning db; + + private RandomValueStringGenerator generator = new RandomValueStringGenerator(8); + + @Before + public void createDatasource() throws Exception { + db = new JdbcClientMetadataProvisioning(jdbcTemplate); + } + + @Test(expected = EmptyResultDataAccessException.class) + public void constraintViolation_WhenNoMatchingClientFound() throws Exception { + ClientMetadata clientMetadata = createTestClientMetadata(generator.generate(), true, new URL("http://app.launch/url"), base64EncodedImg); + db.update(clientMetadata); + } + + @Test + public void retrieveClientMetadata() throws Exception { + String clientId = generator.generate(); + jdbcTemplate.execute("insert into oauth_client_details(client_id, identity_zone_id) values ('" + clientId + "', '" + IdentityZone.getUaa().getId() + "')"); + ClientMetadata clientMetadata = createTestClientMetadata(clientId, true, new URL("http://app.launch/url"), base64EncodedImg); + ClientMetadata createdClientMetadata = db.update(clientMetadata); + + ClientMetadata retrievedClientMetadata = db.retrieve(createdClientMetadata.getClientId()); + + assertThat(retrievedClientMetadata.getClientId(), is(clientMetadata.getClientId())); + assertThat(retrievedClientMetadata.getIdentityZoneId(), is(IdentityZone.getUaa().getId())); + assertThat(retrievedClientMetadata.isShowOnHomePage(), is(clientMetadata.isShowOnHomePage())); + assertThat(retrievedClientMetadata.getAppLaunchUrl(), is(clientMetadata.getAppLaunchUrl())); + assertThat(retrievedClientMetadata.getAppIcon(), is(clientMetadata.getAppIcon())); + } + + @Test(expected = EmptyResultDataAccessException.class) + public void retrieveClientMetadata_ThatDoesNotExist() throws Exception { + String clientId = generator.generate(); + db.retrieve(clientId); + } + + @Test + public void retrieveAllClientMetadata() throws Exception { + String clientId = generator.generate(); + jdbcTemplate.execute("insert into oauth_client_details(client_id, identity_zone_id) values ('" + clientId + "', '" + IdentityZone.getUaa().getId() + "')"); + ClientMetadata clientMetadata1 = createTestClientMetadata(clientId, true, new URL("http://app.launch/url"), base64EncodedImg); + db.update(clientMetadata1); + String clientId2 = generator.generate(); + jdbcTemplate.execute("insert into oauth_client_details(client_id, identity_zone_id) values ('" + clientId2 + "', '" + IdentityZone.getUaa().getId() + "')"); + ClientMetadata clientMetadata2 = createTestClientMetadata(clientId2, true, new URL("http://app.launch/url"), base64EncodedImg); + db.update(clientMetadata2); + + List clientMetadatas = db.retrieveAll(); + + + assertThat(clientMetadatas, PredicateMatcher.has(m -> m.getClientId().equals(clientId))); + assertThat(clientMetadatas, PredicateMatcher.has(m -> m.getClientId().equals(clientId2))); + } + + @Test + public void updateClientMetadata() throws Exception { + String clientId = generator.generate(); + jdbcTemplate.execute("insert into oauth_client_details(client_id, identity_zone_id) values ('" + clientId + "', '" + IdentityZone.getUaa().getId() + "')"); + ClientMetadata newClientMetadata = createTestClientMetadata(clientId, false, new URL("http://updated.app/launch/url"), base64EncodedImg); + + ClientMetadata updatedClientMetadata = db.update(newClientMetadata); + + assertThat(updatedClientMetadata.getClientId(), is(clientId)); + assertThat(updatedClientMetadata.getIdentityZoneId(), is(IdentityZone.getUaa().getId())); + assertThat(updatedClientMetadata.isShowOnHomePage(), is(newClientMetadata.isShowOnHomePage())); + assertThat(updatedClientMetadata.getAppLaunchUrl(), is(newClientMetadata.getAppLaunchUrl())); + assertThat(updatedClientMetadata.getAppIcon(), is(newClientMetadata.getAppIcon())); + } + + private ClientMetadata createTestClientMetadata(String clientId, boolean showOnHomePage, URL appLaunchUrl, String appIcon) throws MalformedURLException { + ClientMetadata clientMetadata = new ClientMetadata(); + clientMetadata.setClientId(clientId); + clientMetadata.setShowOnHomePage(showOnHomePage); + clientMetadata.setAppLaunchUrl(appLaunchUrl); + clientMetadata.setAppIcon(appIcon); + return clientMetadata; + } + + private static final String base64EncodedImg = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAXRQTFRFAAAAOjo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ozk4Ojo6Ojk5NkZMFp/PFqDPNkVKOjo6Ojk5MFhnEq3nEqvjEqzjEbDpMFdlOjo5Ojo6Ojo6Ozg2GZ3TFqXeFKfgF6DVOjo6Ozg2G5jPGZ7ZGKHbGZvROjo6Ojo5M1FfG5vYGp3aM1BdOjo6Ojo6Ojk4KHWeH5PSHpTSKHSbOjk4Ojo6Ojs8IY/QIY/QOjs7Ojo6Ojo6Ozc0JYfJJYjKOzYyOjo5Ozc0KX7AKH/AOzUxOjo5Ojo6Ojo6Ojo6Ojs8LHi6LHi6Ojs7Ojo6Ojo6Ojo6Ojo6Ojo6L3K5L3S7LnW8LnS7Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6NlFvMmWeMmaeNVJwOjo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojk5Ojk4Ojk4Ojk5Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6FaXeFabfGZ/aGKDaHJnVG5rW////xZzURgAAAHV0Uk5TAAACPaXbAVzltTa4MykoM5HlPY/k5Iw85QnBs2D7+lzAtWD7+lyO6EKem0Ey47Mx2dYvtVZVop5Q2i4qlZAnBiGemh0EDXuddqypcHkShPJwYufmX2rvihSJ+qxlg4JiqP2HPtnW1NjZ2svRVAglGTi91RAXr3/WIQAAAAFiS0dEe0/StfwAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADVSURBVBjTY2BgYGBkYmZhZWVhZmJkAANGNnYODk5ODg52NrAIIyMXBzcPLx8/NwcXIyNYQEBQSFhEVExcQgAiICklLSNbWiYnLy0lCRFQUFRSLq9QUVVUgAgwqqlraFZWaWmrqzFCTNXR1dM3MDQy1tWB2MvIaMJqamZuYWnCCHeIlbWNrZ0VG5QPFLF3cHRydoErcHVz9/D08nb3kYSY6evnHxAYFBwSGhYeAbbWNzIqOiY2Lj4hMckVoiQ5JTUtPSMzKzsH6pfcvPyCwqKc4pJcoAAA2pghnaBVZ0kAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTAtMDhUMTI6NDg6MDkrMDA6MDDsQS6eAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE1LTEwLTA4VDEyOjQ4OjA5KzAwOjAwnRyWIgAAAEZ0RVh0c29mdHdhcmUASW1hZ2VNYWdpY2sgNi43LjgtOSAyMDE0LTA1LTEyIFExNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ9yG7QAAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6aGVpZ2h0ADE5Mg8AcoUAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgAMTky06whCAAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNDQ0MzA4NDg5qdC9PQAAAA90RVh0VGh1bWI6OlNpemUAMEJClKI+7AAAAFZ0RVh0VGh1bWI6OlVSSQBmaWxlOi8vL21udGxvZy9mYXZpY29ucy8yMDE1LTEwLTA4LzJiMjljNmYwZWRhZWUzM2ViNmM1Mzg4ODMxMjg3OTg1Lmljby5wbmdoJKG+AAAAAElFTkSuQmCC"; + +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java index c5134e9625e..c3795782b7e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java @@ -42,6 +42,7 @@ import java.util.Map; import static org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR; +import static org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition.PROVIDER_DESCRIPTION; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EXTERNAL_GROUPS_WHITELIST; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.KEYSTONE; @@ -85,6 +86,8 @@ public void testLdapBootstrap() throws Exception { IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, new MockEnvironment()); HashMap ldapConfig = new HashMap<>(); ldapConfig.put(EMAIL_DOMAIN_ATTR, Arrays.asList("test.domain")); + final String idpDescription = "Test LDAP Provider Description"; + ldapConfig.put(PROVIDER_DESCRIPTION, idpDescription); List attrMap = new ArrayList<>(); attrMap.add("value"); ldapConfig.put(EXTERNAL_GROUPS_WHITELIST, attrMap); @@ -104,6 +107,7 @@ public void testLdapBootstrap() throws Exception { assertEquals("test.domain", ldapProvider.getConfig().getEmailDomain().get(0)); assertEquals(Arrays.asList("value"), ldapProvider.getConfig().getExternalGroupsWhitelist()); assertEquals("first_name", ldapProvider.getConfig().getAttributeMappings().get("given_name")); + assertEquals(idpDescription, ldapProvider.getConfig().getProviderDescription()); } @Test diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java index 3787f2c648b..1d9f8994a08 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java @@ -159,14 +159,13 @@ public void acceptInvitePage_for_unverifiedSamlUser() throws Exception { when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); IdentityProvider provider = new IdentityProvider(); - SamlIdentityProviderDefinition definition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition definition = new SamlIdentityProviderDefinition() .setMetaDataLocation("http://test.saml.com") .setIdpEntityAlias("test-saml") .setNameID("test") .setLinkText("testsaml") .setIconUrl("test.com") - .setZoneId(IdentityZone.getUaa().getId()) - .build(); + .setZoneId(IdentityZone.getUaa().getId()); provider.setConfig(definition); provider.setType(OriginKeys.SAML); when(providerProvisioning.retrieveByOrigin(eq("test-saml"), anyString())).thenReturn(provider); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java index 38a0037a536..c9c9a2797b7 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java @@ -1,6 +1,7 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; +import org.cloudfoundry.identity.uaa.authentication.InvalidCodeException; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.manager.AutologinAuthenticationManager; @@ -8,22 +9,29 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.authentication.InvalidCodeException; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; import java.sql.Timestamp; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Map; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsInstanceOf.instanceOf; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /******************************************************************************* @@ -43,13 +51,18 @@ public class AutologinAuthenticationManagerTest { private AutologinAuthenticationManager manager; private ExpiringCodeStore codeStore; private Authentication authenticationToken; + private UaaUserDatabase userDatabase; + private ClientDetailsService clientDetailsService; @Before public void setUp() { manager = new AutologinAuthenticationManager(); - codeStore = Mockito.mock(ExpiringCodeStore.class); - + codeStore = mock(ExpiringCodeStore.class); + userDatabase = mock(UaaUserDatabase.class); + clientDetailsService = mock(ClientDetailsService.class); manager.setExpiringCodeStore(codeStore); + manager.setClientDetailsService(clientDetailsService); + manager.setUserDatabase(userDatabase); Map info = new HashMap<>(); info.put("code", "the_secret_code"); UaaAuthenticationDetails details = new UaaAuthenticationDetails(new MockHttpServletRequest(), "test-client-id"); @@ -66,6 +79,27 @@ public void authentication_successful() throws Exception { codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); + when(clientDetailsService.loadClientByClientId(eq("test-client-id"))).thenReturn(new BaseClientDetails("test-client-details","","","","")); + when(userDatabase.retrieveUserById(eq("test-user-id"))) + .thenReturn( + new UaaUser("test-user-id", + "test-username", + "password", + "email@email.com", + Collections.EMPTY_LIST, + "given name", + "family name", + new Date(System.currentTimeMillis()), + new Date(System.currentTimeMillis()), + OriginKeys.UAA, + "test-external-id", + true, + IdentityZoneHolder.get().getId(), + "test-salt", + new Date(System.currentTimeMillis()) + ) + ); + Authentication authenticate = manager.authenticate(authenticationToken); assertThat(authenticate, is(instanceOf(UaaAuthentication.class))); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index d26ac88e8a7..2aab0fe3e3c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -6,12 +6,10 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.InMemoryExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.login.LoginInfoEndpoint; -import org.cloudfoundry.identity.uaa.login.Prompt; -import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; -import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; +import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; @@ -475,15 +473,14 @@ private List getIdps() { } private SamlIdentityProviderDefinition createIdentityProviderDefinition(String idpEntityAlias, String zoneId) { - SamlIdentityProviderDefinition idp1 = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition idp1 = new SamlIdentityProviderDefinition() .setMetaDataLocation("metadataLocation for " + idpEntityAlias) .setIdpEntityAlias(idpEntityAlias) .setNameID("nameID for " + idpEntityAlias) .setMetadataTrustCheck(true) .setLinkText("link text for " + idpEntityAlias) .setIconUrl("icon url for " + idpEntityAlias) - .setZoneId(zoneId) - .build(); + .setZoneId(zoneId); idp1.setIdpEntityAlias(idpEntityAlias); idp1.setShowSamlLink(true); idp1.setZoneId(zoneId); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java index a0654a1c58c..0bf4ce9afd6 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java @@ -28,6 +28,8 @@ import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -81,6 +83,7 @@ public class ResetPasswordControllerTest extends TestClassNullifier { private ResetPasswordService resetPasswordService; private MessageService messageService; private ExpiringCodeStore codeStore; + private UaaUserDatabase userDatabase; @Autowired @Qualifier("mailTemplateEngine") @@ -93,7 +96,9 @@ public void setUp() throws Exception { resetPasswordService = mock(ResetPasswordService.class); messageService = mock(MessageService.class); codeStore = mock(ExpiringCodeStore.class); - ResetPasswordController controller = new ResetPasswordController(resetPasswordService, messageService, templateEngine, new UaaUrlUtils(), "pivotal", codeStore); + userDatabase = mock(UaaUserDatabase.class); + when(userDatabase.retrieveUserById(anyString())).thenReturn(new UaaUser("username","password","email","givenname","familyname")); + ResetPasswordController controller = new ResetPasswordController(resetPasswordService, messageService, templateEngine, new UaaUrlUtils(), "pivotal", codeStore, userDatabase); InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/jsp"); @@ -242,10 +247,10 @@ public void testInstructions() throws Exception { @Test public void testResetPasswordPage() throws Exception { - ExpiringCode code = new ExpiringCode("code1", new Timestamp(System.currentTimeMillis()), "someData", null); + ExpiringCode code = new ExpiringCode("code1", new Timestamp(System.currentTimeMillis()), "{\"user_id\" : \"some-user-id\"}", null); when(codeStore.generateCode(anyString(), any(Timestamp.class), eq(null))).thenReturn(code); when(codeStore.retrieveCode(anyString())).thenReturn(code); - mockMvc.perform(get("/reset_password").param("email", "user@example.com").param("code", "secret_code")) + mockMvc.perform(get("/reset_password").param("email", "user@example.com").param("code", "code1")) .andExpect(status().isOk()) .andExpect(view().name("reset_password")); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityProviderConfiguratorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityProviderConfiguratorTests.java index d2613c7e05c..5c5d0481e57 100755 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityProviderConfiguratorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityProviderConfiguratorTests.java @@ -181,7 +181,7 @@ public static void initializeOpenSAML() throws Exception { public void setUp() throws Exception { conf = new SamlIdentityProviderConfigurator(); conf.setParserPool(new BasicParserPool()); - singleAdd = SamlIdentityProviderDefinition.Builder.get() + singleAdd = new SamlIdentityProviderDefinition() .setMetaDataLocation(String.format(xmlWithoutID, new RandomValueStringGenerator().generate())) .setIdpEntityAlias(singleAddAlias) .setNameID("sample-nameID") @@ -189,9 +189,8 @@ public void setUp() throws Exception { .setMetadataTrustCheck(true) .setLinkText("sample-link-test") .setIconUrl("sample-icon-url") - .setZoneId("uaa") - .build(); - singleAddWithoutHeader = SamlIdentityProviderDefinition.Builder.get() + .setZoneId("uaa"); + singleAddWithoutHeader = new SamlIdentityProviderDefinition() .setMetaDataLocation(String.format(xmlWithoutHeader, new RandomValueStringGenerator().generate())) .setIdpEntityAlias(singleAddAlias) .setNameID("sample-nameID") @@ -199,8 +198,7 @@ public void setUp() throws Exception { .setMetadataTrustCheck(true) .setLinkText("sample-link-test") .setIconUrl("sample-icon-url") - .setZoneId("uaa") - .build(); + .setZoneId("uaa"); } private static Map> parseYaml(String sampleYaml) { @@ -303,7 +301,7 @@ public void testGetIdentityProviderDefinitionsForZone() throws Exception { String zoneId = UUID.randomUUID().toString(); IdentityZone zone = MultitenancyFixture.identityZone(zoneId, "test-zone"); - SamlIdentityProviderDefinition samlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition samlIdentityProviderDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(xml) .setIdpEntityAlias("zoneIdpAlias") .setNameID("sample-nameID") @@ -311,8 +309,7 @@ public void testGetIdentityProviderDefinitionsForZone() throws Exception { .setMetadataTrustCheck(true) .setLinkText("sample-link-test") .setIconUrl("sample-icon-url") - .setZoneId(zoneId) - .build(); + .setZoneId(zoneId); conf.addSamlIdentityProviderDefinition(samlIdentityProviderDefinition); List idps = conf.getIdentityProviderDefinitionsForZone(zone); @@ -338,7 +335,7 @@ public void testGetIdentityProviderDefinititonsForAllowedProviders() throws Exce public void testReturnAllIdpsInZoneForClientWithNoAllowedProviders() throws Exception { conf.setIdentityProviders(sampleData); conf.afterPropertiesSet(); - SamlIdentityProviderDefinition samlIdentityProviderDefinitionInOtherZone = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition samlIdentityProviderDefinitionInOtherZone = new SamlIdentityProviderDefinition() .setMetaDataLocation(xml) .setIdpEntityAlias("zoneIdpAlias") .setNameID("sample-nameID") @@ -346,8 +343,7 @@ public void testReturnAllIdpsInZoneForClientWithNoAllowedProviders() throws Exce .setMetadataTrustCheck(true) .setLinkText("sample-link-test") .setIconUrl("sample-icon-url") - .setZoneId("other-zone-id") - .build(); + .setZoneId("other-zone-id"); try { conf.addSamlIdentityProviderDefinition(samlIdentityProviderDefinitionInOtherZone); } catch (MetadataProviderException e) { @@ -362,7 +358,7 @@ public void testReturnNoIdpsInZoneForClientWithNoAllowedProviders() throws Excep conf.setIdentityProviders(sampleData); conf.afterPropertiesSet(); String xmlMetadata = String.format(xmlWithoutID, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition samlIdentityProviderDefinitionInOtherZone = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition samlIdentityProviderDefinitionInOtherZone = new SamlIdentityProviderDefinition() .setMetaDataLocation(xmlMetadata) .setIdpEntityAlias("zoneIdpAlias") .setNameID("sample-nameID") @@ -370,8 +366,7 @@ public void testReturnNoIdpsInZoneForClientWithNoAllowedProviders() throws Excep .setMetadataTrustCheck(true) .setLinkText("sample-link-test") .setIconUrl("sample-icon-url") - .setZoneId("other-zone-id") - .build(); + .setZoneId("other-zone-id"); conf.addSamlIdentityProviderDefinition(samlIdentityProviderDefinitionInOtherZone); List clientIdps = conf.getIdentityProviderDefinitions(null, IdentityZoneHolder.get()); @@ -487,13 +482,12 @@ public void testDuplicate_EntityID_IsRejected() throws Exception { conf.afterPropertiesSet(); testGetIdentityProviderDefinitions(3, false); - SamlIdentityProviderDefinition def = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition() .setMetaDataLocation("http://simplesamlphp.identity.cf-app.com/saml2/idp/metadata.php") .setIdpEntityAlias("simplesamlphp-url-2") .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setZoneId(IdentityZone.getUaa().getId()) - .setShowSamlLink(true) - .build(); + .setShowSamlLink(true); //duplicate entityID - different alias ExtendedMetadataDelegate[] delegate = null; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java index b61e5f70d68..dc6cd59f873 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java @@ -20,7 +20,7 @@ public class SamlIdentityProviderDefinitionTests { @Before public void createDefinition() { - definition = SamlIdentityProviderDefinition.Builder.get() + definition = new SamlIdentityProviderDefinition() .setMetaDataLocation("location") .setIdpEntityAlias("alias") .setNameID("nameID") @@ -28,8 +28,7 @@ public void createDefinition() { .setShowSamlLink(false) .setLinkText("link test") .setIconUrl("url") - .setZoneId("zoneId") - .build(); + .setZoneId("zoneId"); } @Test diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java index b3f44533b76..0482c14bb6c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java @@ -24,14 +24,13 @@ public class SamlRedirectUtilsTest { @Test public void testGetIdpRedirectUrl() throws Exception { SamlIdentityProviderDefinition definition = - SamlIdentityProviderDefinition.Builder.get() + new SamlIdentityProviderDefinition() .setMetaDataLocation("http://some.meta.data") .setIdpEntityAlias("simplesamlphp-url") .setNameID("nameID") .setMetadataTrustCheck(true) .setLinkText("link text") - .setZoneId(IdentityZone.getUaa().getId()) - .build(); + .setZoneId(IdentityZone.getUaa().getId()); String url = SamlRedirectUtils.getIdpRedirectUrl(definition, "login.identity.cf-app.com"); Assert.assertEquals("saml/discovery?returnIDParam=idp&entityID=login.identity.cf-app.com&idp=simplesamlphp-url&isPassive=true", url); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java index 1f2f4546809..eb0839e10da 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java @@ -13,20 +13,28 @@ package org.cloudfoundry.identity.uaa.scim.bootstrap; import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupMembershipManager; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.test.TestUtils; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; +import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.junit.Before; import org.junit.Test; +import org.springframework.core.env.MapPropertySource; import org.springframework.jdbc.core.JdbcTemplate; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; public class ScimGroupBootstrapTests extends JdbcTestBase { @@ -100,4 +108,46 @@ public void canAddMembers() throws Exception { assertEquals(3, bootstrap.getGroup("org1.engg").getMembers().size()); assertEquals(2, mDB.getMembers(bootstrap.getGroup("org1.dev").getId(), ScimGroupMember.Role.WRITER).size()); } + + @Test + public void stripsWhitespaceFromGroupNamesAndDescriptions() throws Exception { + bootstrap.setGroups("print|Access the network printer, something| Do something else "); + bootstrap.afterPropertiesSet(); + + ScimGroup group; + assertNotNull(group = bootstrap.getGroup("something")); + assertNotNull(group = gDB.retrieve(group.getId())); + assertEquals("something", group.getDisplayName()); + assertEquals("Do something else", group.getDescription()); + } + + @Test + public void prefersNonBlankYmlOverMessagesProperties() throws Exception { + // set up default groups + HashMap defaults = new HashMap<>(); + defaults.put("records.read", ""); + defaults.put("pets.cat", "Access the cat"); + defaults.put("pets.dog", "Dog your data"); + bootstrap.setMessageSource(new MapPropertySource("messages.properties", defaults)); + bootstrap.setMessagePropertyNameTemplate("%s"); + bootstrap.setDefaultUserGroups(defaults.keySet()); + + // set up configured groups + bootstrap.setGroups("print|Access the network printer,records.read|Read important data,pets.cat|Pet the cat,pets.dog,fish.nemo"); + + bootstrap.afterPropertiesSet(); + + List groups = gDB.retrieveAll(); + + // print: only specified in the configured groups, so it should get its description from there + assertThat(groups, PredicateMatcher.has(group -> "print".equals(group.getDisplayName()) && "Access the network printer".equals(group.getDescription()))); + // records.read: exists in the message property source but should get its description from configuration + assertThat(groups, PredicateMatcher.has(group -> "records.read".equals(group.getDisplayName()) && "Read important data".equals(group.getDescription()))); + // pets.cat: read: exists in the message property source but should get its description from configuration + assertThat(groups, PredicateMatcher.has(group -> "pets.cat".equals(group.getDisplayName()) && "Pet the cat".equals(group.getDescription()))); + // pets.dog: specified in configuration with no description, so it should retain the default description + assertThat(groups, PredicateMatcher.has(group -> "pets.dog".equals(group.getDisplayName()) && "Dog your data".equals(group.getDescription()))); + // fish.nemo: never gets a description + assertThat(groups, PredicateMatcher.has(group -> "fish.nemo".equals(group.getDisplayName()) && group.getDescription() == null)); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java index 9e235fd7245..d95f0272c3c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java @@ -1,6 +1,6 @@ /******************************************************************************* * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. @@ -12,12 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; -import java.sql.Timestamp; -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; @@ -27,7 +21,14 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; import org.junit.Test; -import org.springframework.util.StringUtils; + +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.springframework.util.StringUtils.hasText; public class JdbcScimGroupProvisioningTests extends JdbcTestBase { @@ -52,13 +53,22 @@ private void validateGroupCount(int expected) { assertEquals(expected, existingGroupCount); } - private void validateGroup(ScimGroup group, String name) { + private void validateGroup(ScimGroup group, String name, String zoneId) { + + } + private void validateGroup(ScimGroup group, String name, String zoneId, String description) { assertNotNull(group); assertNotNull(group.getId()); assertNotNull(group.getDisplayName()); - if (StringUtils.hasText(name)) { + if (hasText(name)) { assertEquals(name, group.getDisplayName()); } + if (hasText(description)) { + assertEquals(description, group.getDescription()); + } + if (hasText(zoneId)) { + assertEquals(zoneId, group.getZoneId()); + } } @Test @@ -66,7 +76,7 @@ public void canRetrieveGroups() throws Exception { List groups = dao.retrieveAll(); assertEquals(3, groups.size()); for (ScimGroup g : groups) { - validateGroup(g, null); + validateGroup(g, null, IdentityZoneHolder.get().getId()); } } @@ -118,7 +128,7 @@ public void cannotRetrieveGroupsWithWrongFilter() { @Test public void canRetrieveGroup() throws Exception { ScimGroup group = dao.retrieve("g1"); - validateGroup(group, "uaa.user"); + validateGroup(group, "uaa.user", IdentityZoneHolder.get().getId()); } @Test(expected = ScimResourceNotFoundException.class) @@ -129,12 +139,13 @@ public void cannotRetrieveNonExistentGroup() { @Test public void canCreateGroup() throws Exception { ScimGroup g = new ScimGroup(null, "test.1", IdentityZoneHolder.get().getId()); + g.setDescription("description-create"); ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_MEMBER); ScimGroupMember m2 = new ScimGroupMember("m2", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN); g.setMembers(Arrays.asList(m1, m2)); g = dao.create(g); validateGroupCount(4); - validateGroup(g, "test.1"); + validateGroup(g, "test.1", IdentityZoneHolder.get().getId(), "description-create"); } @Test @@ -164,11 +175,12 @@ public void canUpdateGroup() throws Exception { ScimGroupMember m2 = new ScimGroupMember("g2", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN); g.setMembers(Arrays.asList(m1, m2)); g.setDisplayName("uaa.none"); + g.setDescription("description-update"); - g = dao.update("g1", g); + dao.update("g1", g); g = dao.retrieve("g1"); - validateGroup(g, "uaa.none"); + validateGroup(g, "uaa.none", IdentityZoneHolder.get().getId(), "description-update"); } @Test @@ -181,12 +193,13 @@ private ScimGroup addGroup(String id, String name) { TestUtils.assertNoSuchUser(jdbcTemplate, "id", id); //"id,displayName,created,lastModified,version,identity_zone_id" jdbcTemplate.update(dao.ADD_GROUP_SQL, - id, - name, - new Timestamp(System.currentTimeMillis()), - new Timestamp(System.currentTimeMillis()), - 0, - IdentityZoneHolder.get().getId()); + id, + name, + name+"-description", + new Timestamp(System.currentTimeMillis()), + new Timestamp(System.currentTimeMillis()), + 0, + IdentityZoneHolder.get().getId()); return dao.retrieve(id); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java index 91e8a264e25..91b84839a88 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java @@ -23,16 +23,18 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import java.sql.Timestamp; -import java.util.Arrays; import java.util.Collections; import java.util.UUID; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -58,6 +60,8 @@ public class JdbcUaaUserDatabaseTests extends JdbcTestBase { private JdbcTemplate template; + public static final String ADD_GROUP_SQL = "insert into groups (id, displayName, identity_zone_id) values (?,?,?)"; + public static final String ADD_MEMBER_SQL = "insert into group_membership (group_id, member_id, member_type, authorities) values (?,?,?,?)"; private void addUser(String id, String name, String password) { TestUtils.assertNoSuchUser(template, "id", id); @@ -66,14 +70,14 @@ private void addUser(String id, String name, String password) { } private void addAuthority(String authority, String userId) { - String authorities = template.queryForObject(getAuthoritiesSql, String.class, userId); - authorities = authorities == null ? authority : authorities + "," + authority; - template.update(addAuthoritySql, authorities, userId); + String id = new RandomValueStringGenerator().generate(); + jdbcTemplate.update(ADD_GROUP_SQL, id, authority, IdentityZoneHolder.get().getId()); + jdbcTemplate.update(ADD_MEMBER_SQL, id, userId, "USER", "MEMBER"); } @Before public void initializeDb() throws Exception { - + IdentityZoneHolder.clear(); otherIdentityZone = new IdentityZone(); otherIdentityZone.setId("some-other-zone-id"); @@ -102,7 +106,10 @@ public void clearDb() throws Exception { @Test public void addedUserHasNoLegacyVerificationBehavior() { - Arrays.asList(JOE_ID, MABEL_ID, ALICE_ID).stream().map(id -> db.retrieveUserById(id)).forEach(user -> assertFalse(user.isLegacyVerificationBehavior())); + assertFalse(db.retrieveUserById(JOE_ID).isLegacyVerificationBehavior()); + assertFalse(db.retrieveUserById(MABEL_ID).isLegacyVerificationBehavior()); + IdentityZoneHolder.set(otherIdentityZone); + assertFalse(db.retrieveUserById(ALICE_ID).isLegacyVerificationBehavior()); } @Test @@ -158,6 +165,46 @@ public void getUserWithExtraAuthorities() { joe.getAuthorities().contains(new SimpleGrantedAuthority("dash.admin"))); } + @Test + public void getUserWithNestedAuthoritiesWorks() { + UaaUser joe = db.retrieveUserByName("joe", OriginKeys.UAA); + assertThat(joe.getAuthorities(), + containsInAnyOrder( + new SimpleGrantedAuthority("uaa.user") + ) + ); + + String directId = new RandomValueStringGenerator().generate(); + String indirectId = new RandomValueStringGenerator().generate(); + + jdbcTemplate.update(ADD_GROUP_SQL, directId, "direct", IdentityZoneHolder.get().getId()); + jdbcTemplate.update(ADD_GROUP_SQL, indirectId, "indirect", IdentityZoneHolder.get().getId()); + jdbcTemplate.update(ADD_MEMBER_SQL, indirectId, directId, "GROUP", "MEMBER"); + jdbcTemplate.update(ADD_MEMBER_SQL, directId, joe.getId(), "USER", "MEMBER"); + + + evaluateNestedJoe(); + + //add a circular group + jdbcTemplate.update(ADD_MEMBER_SQL, directId, indirectId, "GROUP", "MEMBER"); + + evaluateNestedJoe(); + } + + protected void evaluateNestedJoe() { + UaaUser joe; + joe = db.retrieveUserByName("joe", OriginKeys.UAA); + + assertThat(joe.getAuthorities(), + containsInAnyOrder( + new SimpleGrantedAuthority("direct"), + new SimpleGrantedAuthority("uaa.user"), + new SimpleGrantedAuthority("indirect") + ) + ); + } + + @Test(expected = UsernameNotFoundException.class) public void getValidUserInDefaultZoneFromOtherZoneFails() { IdentityZoneHolder.set(otherIdentityZone); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java index b1042a09708..aa917101f4d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java @@ -91,24 +91,22 @@ public void setUp() throws Exception { client = new BaseClientDetails("clientid","", "", "","",""); uaaDef = new UaaIdentityProviderDefinition(null, null); ldapDef = new LdapIdentityProviderDefinition(); - samlDef1 = SamlIdentityProviderDefinition.Builder.get() + samlDef1 = new SamlIdentityProviderDefinition() .setMetaDataLocation(idpMetaData) .setIdpEntityAlias("") .setNameID("") .setMetadataTrustCheck(true) .setLinkText("") .setIconUrl("") - .setZoneId(IdentityZone.getUaa().getId()) - .build(); - samlDef2 = SamlIdentityProviderDefinition.Builder.get() + .setZoneId(IdentityZone.getUaa().getId()); + samlDef2 = new SamlIdentityProviderDefinition() .setMetaDataLocation(idpMetaData) .setIdpEntityAlias("") .setNameID("") .setMetadataTrustCheck(true) .setLinkText("") .setIconUrl("") - .setZoneId(IdentityZone.getUaa().getId()) - .build(); + .setZoneId(IdentityZone.getUaa().getId()); configureTestData(); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/util/PredicateMatcher.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/PredicateMatcher.java new file mode 100644 index 00000000000..165e50c621f --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/util/PredicateMatcher.java @@ -0,0 +1,49 @@ +package org.cloudfoundry.identity.uaa.util; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.util.function.Predicate; + +import static org.hamcrest.Matchers.hasItem; + +public class PredicateMatcher extends BaseMatcher { + + private PredicateMatcher(){} + + private Predicate predicate; + + public static PredicateMatcher is(Predicate predicate) { + PredicateMatcher matcher = new PredicateMatcher<>(); + matcher.predicate = predicate; + return matcher; + } + + public static PredicateMatcher[] are(Predicate... predicates) { + PredicateMatcher[] matchers = new PredicateMatcher[predicates.length]; + for(int i = 0; i < predicates.length; i++) { + matchers[i] = is(predicates[i]); + } + return matchers; + } + + public static Matcher> has(Predicate predicate) { + PredicateMatcher itemMatcher = is(predicate); + return hasItem(itemMatcher); + } + + @Override + public boolean matches(Object item) { + try { + return predicate.test((T) item); + } catch(ClassCastException ex) { + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("match for a predicate"); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java index 5ebf86ceece..66d708c1311 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java @@ -2,12 +2,13 @@ import org.apache.commons.lang.RandomStringUtils; import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; -import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdpAlreadyExistsException; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.After; @@ -21,11 +22,11 @@ import java.util.Map; import java.util.UUID; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; public class JdbcIdentityProviderProvisioningTests extends JdbcTestBase { @@ -76,7 +77,7 @@ public void test_cannot_delete_uaa_providers() { //action try to delete uaa provider //should not do anything assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); - IdentityProvider uaa = db.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); + IdentityProvider uaa = db.retrieveByOrigin(UAA, IdentityZoneHolder.get().getId()); db.onApplicationEvent(new EntityDeletedEvent<>(uaa)); assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); } @@ -85,8 +86,11 @@ public void test_cannot_delete_uaa_providers() { public void testCreateAndUpdateIdentityProviderInDefaultZone() throws Exception { String zoneId = IdentityZone.getUaa().getId(); String originKey = RandomStringUtils.randomAlphabetic(6); - IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zoneId); - + IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zoneId); + String providerDescription = "Test Description"; + idp.setConfig(new UaaIdentityProviderDefinition(null,null)); + idp.getConfig().setProviderDescription(providerDescription); + idp.setType(UAA); IdentityProvider createdIdp = db.create(idp); Map rawCreatedIdp = jdbcTemplate.queryForMap("select * from identity_provider where id = ?",createdIdp.getId()); @@ -94,26 +98,27 @@ public void testCreateAndUpdateIdentityProviderInDefaultZone() throws Exception assertEquals(idp.getOriginKey(), createdIdp.getOriginKey()); assertEquals(idp.getType(), createdIdp.getType()); assertEquals(idp.getConfig(), createdIdp.getConfig()); + assertEquals(providerDescription, createdIdp.getConfig().getProviderDescription()); assertEquals(idp.getName(), rawCreatedIdp.get("name")); assertEquals(idp.getOriginKey(), rawCreatedIdp.get("origin_key")); assertEquals(idp.getType(), rawCreatedIdp.get("type")); - assertEquals(idp.getConfig(), JsonUtils.readValue((String)rawCreatedIdp.get("config"), AbstractIdentityProviderDefinition.class)); + assertEquals(idp.getConfig(), JsonUtils.readValue((String)rawCreatedIdp.get("config"), UaaIdentityProviderDefinition.class)); assertEquals(zoneId, rawCreatedIdp.get("identity_zone_id").toString().trim()); idp.setId(createdIdp.getId()); idp.setLastModified(new Timestamp(System.currentTimeMillis())); idp.setName("updated name"); idp.setCreated(createdIdp.getCreated()); - idp.setConfig(new AbstractIdentityProviderDefinition()); + idp.setConfig(new UaaIdentityProviderDefinition()); idp.setOriginKey("new origin key"); - idp.setType("new type"); + idp.setType(UAA); idp.setIdentityZoneId("somerandomID"); createdIdp = db.update(idp); assertEquals(idp.getName(), createdIdp.getName()); assertEquals(rawCreatedIdp.get("origin_key"), createdIdp.getOriginKey()); - assertEquals(OriginKeys.UNKNOWN, createdIdp.getType()); //we don't allow other types anymore + assertEquals(UAA, createdIdp.getType()); //we don't allow other types anymore assertEquals(idp.getConfig(), createdIdp.getConfig()); assertEquals(idp.getLastModified().getTime()/1000, createdIdp.getLastModified().getTime()/1000); assertEquals(Integer.valueOf(rawCreatedIdp.get("version").toString())+1, createdIdp.getVersion()); @@ -123,7 +128,7 @@ public void testCreateAndUpdateIdentityProviderInDefaultZone() throws Exception @Test public void testCreateIdentityProviderInOtherZone() throws Exception { IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); - + IdentityZoneHolder.set(zone); String originKey = RandomStringUtils.randomAlphabetic(6); IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zone.getId()); @@ -154,6 +159,7 @@ public void testCreateIdentityProviderWithNonUniqueOriginKeyInDefaultZone() thro @Test(expected=IdpAlreadyExistsException.class) public void testCreateIdentityProviderWithNonUniqueOriginKeyInOtherZone() throws Exception { IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); + IdentityZoneHolder.set(zone); String originKey = RandomStringUtils.randomAlphabetic(6); IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zone.getId()); db.create(idp); @@ -166,7 +172,9 @@ public void testCreateIdentityProvidersWithSameOriginKeyInBothZones() throws Exc String originKey = RandomStringUtils.randomAlphabetic(6); IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zoneId); db.create(idp); - idp.setIdentityZoneId(MultitenancyFixture.identityZone(UUID.randomUUID().toString(),"myzone").getId()); + IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); + IdentityZoneHolder.set(zone); + idp.setIdentityZoneId(zone.getId()); db.create(idp); } @@ -194,6 +202,7 @@ public void testUpdateIdentityProviderInDefaultZone() throws Exception { @Test public void testUpdateIdentityProviderInOtherZone() throws Exception { IdentityZone zone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(),"myzone"); + IdentityZoneHolder.set(zone); String originKey = RandomStringUtils.randomAlphabetic(6); String idpId = RandomStringUtils.randomAlphabetic(6); IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zone.getId()); @@ -220,6 +229,7 @@ public void testRetrieveIdentityProviderById() { IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, uaaZoneId); idp.setId(idpId); IdentityZone zone = MultitenancyFixture.identityZone(identityZoneId, identityZoneId); + IdentityZoneHolder.set(zone); idp.setIdentityZoneId(zone.getId()); idp = db.create(idp); IdentityProvider retrievedIdp = db.retrieve(idp.getId()); @@ -242,6 +252,7 @@ public void testRetrieveAll() throws Exception { assertEquals(numberOfIdps + 1, identityProviders.size()); IdentityZone otherZone = MultitenancyFixture.identityZone(UUID.randomUUID().toString(), "myzone"); + IdentityZoneHolder.set(otherZone); String originKey = RandomStringUtils.randomAlphabetic(6); IdentityProvider otherZoneIdp = MultitenancyFixture.identityProvider(originKey, otherZone.getId()); db.create(otherZoneIdp); @@ -256,7 +267,7 @@ public void testRetrieveIdentityProviderByOriginInSameZone() { String identityZoneId = RandomStringUtils.randomAlphabetic(6); String idpId = RandomStringUtils.randomAlphabetic(6); IdentityZone identityZone = MultitenancyFixture.identityZone(identityZoneId, "myzone"); - + IdentityZoneHolder.set(identityZone); IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, identityZone.getId()); idp.setId(idpId); idp = db.create(idp); diff --git a/uaa/src/main/resources/login.yml b/uaa/src/main/resources/login.yml index 348ac5fc6ad..78101a6a621 100644 --- a/uaa/src/main/resources/login.yml +++ b/uaa/src/main/resources/login.yml @@ -51,9 +51,6 @@ login: #selfServiceLinksEnabled: true # Enable sending invitations on the Login Server (disabled by default) #invitationsEnabled: true - # the brand to use for password reset emails and page titles - # (defaults to oss) - #brand: pivotal #base URL that the login server can be reached at url: http://localhost:8080/uaa @@ -160,6 +157,7 @@ login: # attributeMappings: # given_name: firstName # family_name: surname +# providerDescription: 'Human readable description of this provider' # okta-local-2: # idpMetadata: | # MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG @@ -201,6 +199,14 @@ login: authorize: url: http://localhost:8080/uaa/oauth/authorize +# branding: +# productLogo: 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 +# footerLegalText: This legal text will show up in the footer. +# footerLegalLinks: +# Terms: /exampleTerms +# Privacy Agreement: privacy_example.html +# Licensing: http://example.com/ + uaa: # The hostname of the UAA that this login server will connect to url: http://localhost:8080/uaa diff --git a/uaa/src/main/resources/uaa.yml b/uaa/src/main/resources/uaa.yml index 79ac381083e..8a108ed788b 100755 --- a/uaa/src/main/resources/uaa.yml +++ b/uaa/src/main/resources/uaa.yml @@ -99,6 +99,7 @@ # phone_number: telephonenumber # user.attribute.employeeCostCenter: costCenter # user.attribute.terribleBosses: uaaManager +# providerDescription: 'Human readable description of this provider' #ldap: @@ -289,4 +290,4 @@ oauth: # - GET # - PUT # - POST -# - DELETE \ No newline at end of file +# - DELETE diff --git a/uaa/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp b/uaa/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp deleted file mode 100644 index 4adaa316ea7..00000000000 --- a/uaa/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp +++ /dev/null @@ -1,108 +0,0 @@ -<%-- - - Cloud Foundry 2012.02.03 Beta - Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - - This product is licensed to you under the Apache License, Version 2.0 (the "License"). - You may not use this product except in compliance with the License. - - This product includes a number of subcomponents with - separate copyright notices and license terms. Your use of these - subcomponents is subject to the terms and conditions of the - subcomponent's license, as noted in the LICENSE file. - ---%> -<%@ page session="false"%> - -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%> -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> - - - - -Access Confirmation | Cloud Foundry - - -

    -
    - -
    -

    Sorry

    -

    There was an error. The request for authorization was invalid.

    -
    -
    - - -

    Please Confirm

    -
    - -
    -

    - Do you authorize the application '${client_id}' at ${redirect_uri} to access your Cloud - Foundry resources? -

    - - -

    You have already approved '${client_id}' with access to the following:

    - -
    - -
    -
    - -

    You have already denied '${client_id}' access to the following:

    - -
    - -
    -
    - -

    Do you want to allow '${client_id}' to:

    - -
    - -
    -
    - - -

    Do you wish to change these selections?

    -
    -

    If you do not recognize the application or the URL in the - link above you should deny access by unchecking all these boxes.

    -
    - - -
    - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - - - - diff --git a/uaa/src/main/webapp/WEB-INF/jsp/home.jsp b/uaa/src/main/webapp/WEB-INF/jsp/home.jsp deleted file mode 100644 index 2ce7939f458..00000000000 --- a/uaa/src/main/webapp/WEB-INF/jsp/home.jsp +++ /dev/null @@ -1,54 +0,0 @@ -<%-- - - Cloud Foundry 2012.02.03 Beta - Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - - This product is licensed to you under the Apache License, Version 2.0 (the "License"). - You may not use this product except in compliance with the License. - - This product includes a number of subcomponents with - separate copyright notices and license terms. Your use of these - subcomponents is subject to the terms and conditions of the - subcomponent's license, as noted in the LICENSE file. - ---%> -<%@ page session="false"%> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> - - - -Success | Cloud Foundry - - -
    -
    -
    -

    Success

    - -

    Your account login is working and you have authenticated.

    - - -
    -

    But there was an error.

    -
    -
    - -

    You are logged in.

    - -

    - - Logout   -

    - -
    -
    -
    - Copyright © - - Pivotal Software, Inc. All rights reserved. -
    -
    - - diff --git a/uaa/src/main/webapp/WEB-INF/jsp/login.jsp b/uaa/src/main/webapp/WEB-INF/jsp/login.jsp deleted file mode 100644 index 8791f839bd7..00000000000 --- a/uaa/src/main/webapp/WEB-INF/jsp/login.jsp +++ /dev/null @@ -1,64 +0,0 @@ -<%-- - - Cloud Foundry 2012.02.03 Beta - Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - - This product is licensed to you under the Apache License, Version 2.0 (the "License"). - You may not use this product except in compliance with the License. - - This product includes a number of subcomponents with - separate copyright notices and license terms. Your use of these - subcomponents is subject to the terms and conditions of the - subcomponent's license, as noted in the LICENSE file. - ---%> -<%@ page session="false"%> - -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> - - - - -UAA Login | Cloud Foundry - - - -
    -
    -

    Sign in to zone "${zone_name}" with your CloudFoundry credentials.

    -
    " method="POST" novalidate> -
    - -
    Sorry, we couldn't verify your email and - password.
    -
    - - - -
    - -
    -
    -
    -

    - If you are reading this you are probably in the wrong place because - the UAA does not support a branded UI out of the box. To login to - Pivotal Web Services - click here. If you were - re-directed here by another application, please contact the owner of - that application and tell them to use the Login Server as UI entry - point. -

    -
    -
    - - - diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 25c762db674..21ffaeefa25 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -413,4 +413,11 @@ + + + + + + + diff --git a/uaa/src/main/webapp/WEB-INF/spring/client-admin-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/client-admin-endpoints.xml index a5c1af77376..0937b58b9aa 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/client-admin-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/client-admin-endpoints.xml @@ -42,6 +42,7 @@ + @@ -88,6 +89,13 @@ + + + + + + + diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml index 0ff86a58f22..aa94d08d189 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml @@ -26,9 +26,14 @@ + + + + + + diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml index 433d6b5f06b..6e40db804cc 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -41,6 +41,25 @@ + + + + + + + + + + + + + + + @@ -375,10 +395,6 @@ - - select g.displayName from groups g, group_membership m where g.id = m.group_id and m.member_id = ? - - diff --git a/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml index 2114242cb0f..cb0369e38ed 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml @@ -245,7 +245,7 @@ - + @@ -255,6 +255,7 @@ + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java index 868a8d5782d..42dd930d43b 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java @@ -166,6 +166,10 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw public RequestDispatcher getNamedDispatcher(String path) { return new MockRequestDispatcher("/"); } + + public String getVirtualServerName() { + return null; + } }; context.setServletContext(servletContext); MockServletConfig servletConfig = new MockServletConfig(servletContext); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java new file mode 100644 index 00000000000..02b14312aa7 --- /dev/null +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java @@ -0,0 +1,251 @@ +package org.cloudfoundry.identity.uaa.client; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; +import org.cloudfoundry.identity.uaa.test.TestClient; +import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.PredicateMatcher; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +import java.net.URL; +import java.util.ArrayList; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

    + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

    + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +public class ClientMetadataAdminEndpointsMockMvcTest extends InjectedMockContextTest { + + private String adminClientTokenWithClientsWrite; + private JdbcClientDetailsService clients; + private RandomValueStringGenerator generator = new RandomValueStringGenerator(8); + private TestClient testClient; + private UaaTestAccounts testAccounts; + private String adminClientTokenWithClientsRead; + + @Before + public void setUp() throws Exception { + testClient = new TestClient(getMockMvc()); + testAccounts = UaaTestAccounts.standard(null); + adminClientTokenWithClientsRead = testClient.getClientCredentialsOAuthAccessToken( + testAccounts.getAdminClientId(), + testAccounts.getAdminClientSecret(), + "clients.read"); + adminClientTokenWithClientsWrite = testClient.getClientCredentialsOAuthAccessToken( + testAccounts.getAdminClientId(), + testAccounts.getAdminClientSecret(), + "clients.write"); + + clients = getWebApplicationContext().getBean(JdbcClientDetailsService.class); + } + + @Test + public void getClientMetadata() throws Exception { + String clientId = generator.generate(); + + String marissaToken = getUserAccessToken(clientId); + MockHttpServletResponse response = getTestClientMetadata(clientId, marissaToken); + + assertThat(response.getStatus(), is(HttpStatus.OK.value())); + } + + private String getUserAccessToken(String clientId) throws Exception { + BaseClientDetails newClient = new BaseClientDetails(clientId, "oauth", "oauth.approvals", "password", "oauth.login"); + newClient.setClientSecret("secret"); + clients.addClientDetails(newClient); + return testClient.getUserOAuthAccessToken(clientId, "secret", "marissa", "koala", "oauth.approvals"); + } + + @Test + public void getClientMetadata_WhichDoesNotExist() throws Exception { + String clientId = generator.generate(); + + MockHttpServletResponse response = getTestClientMetadata(clientId, adminClientTokenWithClientsRead); + + assertThat(response.getStatus(), is(HttpStatus.NOT_FOUND.value())); + } + + @Test + public void getAllClientMetadata() throws Exception { + String clientId1 = generator.generate(); + String marissaToken = getUserAccessToken(clientId1); + + String clientId2 = generator.generate(); + clients.addClientDetails(new BaseClientDetails(clientId2, null, null, null, null)); + + String clientId3 = generator.generate(); + clients.addClientDetails(new BaseClientDetails(clientId3, null, null, null, null)); + ClientMetadata client3Metadata = new ClientMetadata(); + client3Metadata.setClientId(clientId3); + client3Metadata.setIdentityZoneId("uaa"); + client3Metadata.setAppLaunchUrl(new URL("http://client3.com/app")); + client3Metadata.setShowOnHomePage(true); + client3Metadata.setAppIcon("Y2xpZW50IDMgaWNvbg=="); + performUpdate(client3Metadata); + + String clientId4 = generator.generate(); + clients.addClientDetails(new BaseClientDetails(clientId4, null, null, null, null)); + ClientMetadata client4Metadata = new ClientMetadata(); + client4Metadata.setClientId(clientId4); + client4Metadata.setIdentityZoneId("uaa"); + client4Metadata.setAppLaunchUrl(new URL("http://client4.com/app")); + client4Metadata.setAppIcon("aWNvbiBmb3IgY2xpZW50IDQ="); + performUpdate(client4Metadata); + + MockHttpServletResponse response = getMockMvc().perform(get("/oauth/clients/meta") + .header("Authorization", "Bearer " + marissaToken) + .accept(APPLICATION_JSON)).andExpect(status().isOk()).andReturn().getResponse(); + ArrayList clientMetadataList = JsonUtils.readValue(response.getContentAsString(), new TypeReference>() {}); + + assertThat(clientMetadataList, not(PredicateMatcher.has(m -> m.getClientId().equals(clientId1)))); + assertThat(clientMetadataList, not(PredicateMatcher.has(m -> m.getClientId().equals(clientId2)))); + assertThat(clientMetadataList, PredicateMatcher.has(m -> m.getClientId().equals(clientId3) && m.getAppIcon().equals(client3Metadata.getAppIcon()) && m.getAppLaunchUrl().equals(client3Metadata.getAppLaunchUrl()) && m.isShowOnHomePage() == client3Metadata.isShowOnHomePage())); + assertThat(clientMetadataList, PredicateMatcher.has(m -> m.getClientId().equals(clientId4) && m.getAppIcon().equals(client4Metadata.getAppIcon()) && m.getAppLaunchUrl().equals(client4Metadata.getAppLaunchUrl()) && m.isShowOnHomePage() == client4Metadata.isShowOnHomePage())); + } + + @Test + public void updateClientMetadata() throws Exception { + String clientId = generator.generate(); + clients.addClientDetails(new BaseClientDetails(clientId, null, null, null, null)); + + ClientMetadata updatedClientMetadata = new ClientMetadata(); + updatedClientMetadata.setClientId(clientId); + URL appLaunchUrl = new URL("http://changed.app.launch/url"); + updatedClientMetadata.setAppLaunchUrl(appLaunchUrl); + + ResultActions perform = performUpdate(updatedClientMetadata); + assertThat(perform.andReturn().getResponse().getContentAsString(), containsString(appLaunchUrl.toString())); + + MockHttpServletResponse response = getTestClientMetadata(clientId, adminClientTokenWithClientsRead); + assertThat(response.getStatus(), is(HttpStatus.OK.value())); + assertThat(response.getContentAsString(), containsString(appLaunchUrl.toString())); + } + + private ResultActions performUpdate(ClientMetadata updatedClientMetadata) throws Exception { + MockHttpServletRequestBuilder updateClientPut = put("/oauth/clients/" + updatedClientMetadata.getClientId() + "/meta") + .header("Authorization", "Bearer " + adminClientTokenWithClientsWrite) + .header("If-Match", "0") + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(updatedClientMetadata)); + return getMockMvc().perform(updateClientPut); + } + + @Test + public void updateClientMetadata_InsufficientScope() throws Exception { + String clientId = generator.generate(); + String marissaToken = getUserAccessToken(clientId); + + ClientMetadata updatedClientMetadata = new ClientMetadata(); + updatedClientMetadata.setClientId(clientId); + URL appLaunchUrl = new URL("http://changed.app.launch/url"); + updatedClientMetadata.setAppLaunchUrl(appLaunchUrl); + + MockHttpServletRequestBuilder updateClientPut = put("/oauth/clients/" + clientId + "/meta") + .header("Authorization", "Bearer " + marissaToken) + .header("If-Match", "0") + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(updatedClientMetadata)); + MockHttpServletResponse response = getMockMvc().perform(updateClientPut).andReturn().getResponse(); + assertThat(response.getStatus(), is(HttpStatus.FORBIDDEN.value())); + } + + @Test + public void updateClientMetadata_WithNoClientIdInBody() throws Exception { + String clientId = generator.generate(); + clients.addClientDetails(new BaseClientDetails(clientId, null, null, null, null)); + + ClientMetadata updatedClientMetadata = new ClientMetadata(); + updatedClientMetadata.setClientId(null); + URL appLaunchUrl = new URL("http://changed.app.launch/url"); + updatedClientMetadata.setAppLaunchUrl(appLaunchUrl); + + MockHttpServletRequestBuilder updateClientPut = put("/oauth/clients/" + clientId + "/meta") + .header("Authorization", "Bearer " + adminClientTokenWithClientsWrite) + .header("If-Match", "0") + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(updatedClientMetadata)); + ResultActions perform = getMockMvc().perform(updateClientPut); + assertThat(perform.andReturn().getResponse().getContentAsString(), containsString(appLaunchUrl.toString())); + + MockHttpServletResponse response = getTestClientMetadata(clientId, adminClientTokenWithClientsRead); + assertThat(response.getStatus(), is(HttpStatus.OK.value())); + assertThat(response.getContentAsString(), containsString(appLaunchUrl.toString())); + } + + @Test + public void updateClientMetadata_ForNonExistentClient() throws Exception { + String clientId = generator.generate(); + + ClientMetadata clientMetadata = new ClientMetadata(); + clientMetadata.setClientId(clientId); + URL appLaunchUrl = new URL("http://changed.app.launch/url"); + clientMetadata.setAppLaunchUrl(appLaunchUrl); + + MockHttpServletRequestBuilder updateClientPut = put("/oauth/clients/" + clientId + "/meta") + .header("Authorization", "Bearer " + adminClientTokenWithClientsWrite) + .header("If-Match", "0") + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(clientMetadata)); + ResultActions perform = getMockMvc().perform(updateClientPut); + assertEquals(perform.andReturn().getResponse().getStatus(), NOT_FOUND.value()); + } + + @Test + public void updateClientMetadata_ClientIdMismatch() throws Exception { + String clientId = generator.generate(); + clients.addClientDetails(new BaseClientDetails(clientId, null, null, null, null)); + + ClientMetadata clientMetadata = new ClientMetadata(); + clientMetadata.setClientId("other-client-id"); + URL appLaunchUrl = new URL("http://changed.app.launch/url"); + clientMetadata.setAppLaunchUrl(appLaunchUrl); + + MockHttpServletRequestBuilder updateClientPut = put("/oauth/clients/" + clientId + "/meta") + .header("Authorization", "Bearer " + adminClientTokenWithClientsWrite) + .header("If-Match", "0") + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(clientMetadata)); + ResultActions perform = getMockMvc().perform(updateClientPut); + assertEquals(perform.andReturn().getResponse().getStatus(), HttpStatus.BAD_REQUEST.value()); + } + + private MockHttpServletResponse getTestClientMetadata(String clientId, String token) throws Exception { + MockHttpServletRequestBuilder createClientGet = get("/oauth/clients/" + clientId + "/meta") + .header("Authorization", "Bearer " + token) + .accept(APPLICATION_JSON); + return getMockMvc().perform(createClientGet).andReturn().getResponse(); + } +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java index 0e63aaa735f..143e8eaf95f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java @@ -16,6 +16,8 @@ import static org.junit.Assert.assertEquals; import org.cloudfoundry.identity.uaa.ServerRunning; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; @@ -32,6 +34,10 @@ import org.openqa.selenium.WebDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -57,6 +63,8 @@ public class AppApprovalIT { @Rule public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts); + public RestOperations restTemplate; + @Autowired @Rule public IntegrationTestRule integrationTestRule; @@ -73,6 +81,8 @@ public class AppApprovalIT { @Before @After public void logout_and_clear_cookies() { + restTemplate = serverRunning.getRestTemplate(); + try { webDriver.get(baseUrl + "/logout.do"); }catch (org.openqa.selenium.TimeoutException x) { @@ -85,6 +95,20 @@ public void logout_and_clear_cookies() { @Test public void testApprovingAnApp() throws Exception { + ResponseEntity> getGroups = restTemplate.exchange(baseUrl + "/Groups?filter=displayName eq '{displayName}'", + HttpMethod.GET, + null, + new ParameterizedTypeReference>() { + }, + "cloud_controller.read"); + ScimGroup group = getGroups.getBody().getResources().stream().findFirst().get(); + + group.setDescription("Read about your clouds."); + HttpHeaders headers = new HttpHeaders(); + headers.add("If-Match", Integer.toString(group.getVersion())); + HttpEntity request = new HttpEntity(group, headers); + restTemplate.exchange(baseUrl + "/Groups/{group-id}", HttpMethod.PUT, request, Object.class, group.getId()); + ScimUser user = createUnapprovedUser(); // Visit app @@ -100,6 +124,7 @@ public void testApprovingAnApp() throws Exception { webDriver.findElement(By.xpath("//label[text()='Change your password']/preceding-sibling::input")).click(); webDriver.findElement(By.xpath("//label[text()='Translate user ids to names and vice versa']/preceding-sibling::input")).click(); + webDriver.findElement(By.xpath("//label[text()='Read about your clouds.']/preceding-sibling::input")); webDriver.findElement(By.xpath("//button[text()='Authorize']")).click(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java index 874f56acd2a..afeaff8a381 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java @@ -6,13 +6,14 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.MultitenantJdbcClientDetailsService; import org.flywaydb.core.internal.util.StringUtils; import org.junit.After; import org.junit.Before; @@ -105,7 +106,7 @@ public void invite_Multiple_Users_With_Client_Credentials() throws Exception { public void invite_User_With_User_Credentials() throws Exception { String email = "user1@example.com"; String redirectUri = "example.com"; - String userToken = utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret); + String userToken = utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret, null); InvitationsResponse response = sendRequestWithTokenAndReturnResponse(userToken, null, clientId, redirectUri, email); assertResponseAndCodeCorrect(new String[] {email}, redirectUri, null, response, clientDetails); } @@ -143,7 +144,7 @@ public void multiple_Users_Email_Exists_With_One_Origin() throws Exception { user2.setOrigin(UAA); utils().createUser(getMockMvc(), clientAdminToken, user2); - String userToken = utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret); + String userToken = utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret, null); InvitationsResponse response = sendRequestWithTokenAndReturnResponse(userToken, null, clientId, "example.com", email); assertEquals(0, response.getNewInvites().size()); assertEquals(1, response.getFailedInvites().size()); @@ -154,7 +155,7 @@ public void multiple_Users_Email_Exists_With_One_Origin() throws Exception { public void accept_Invitation_Email_With_Oss_Brand() throws Exception { ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "oss"); - getMockMvc().perform(get(getAcceptInvitationLink())) + getMockMvc().perform(get(getAcceptInvitationLink(null))) .andExpect(content().string(containsString("Create your account"))) .andExpect(content().string(not(containsString("Pivotal ID")))) .andExpect(content().string(not(containsString("Create Pivotal ID")))) @@ -165,7 +166,7 @@ public void accept_Invitation_Email_With_Oss_Brand() throws Exception { public void accept_Invitation_Email_With_Pivotal_Brand() throws Exception { ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "pivotal"); - getMockMvc().perform(get(getAcceptInvitationLink())) + getMockMvc().perform(get(getAcceptInvitationLink(null))) .andExpect(content().string(containsString("Create your Pivotal ID"))) .andExpect(content().string(containsString("Pivotal products"))) .andExpect(content().string(not(containsString("Create your account")))) @@ -176,11 +177,21 @@ public void accept_Invitation_Email_With_Pivotal_Brand() throws Exception { @Test public void accept_Invitation_Email_Within_Zone() throws Exception { String subdomain = generator.generate(); - utils().createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext()); + IdentityZone zone = utils().createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext()); ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "pivotal"); - getMockMvc().perform(get(getAcceptInvitationLink()) - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) + BaseClientDetails client = utils().getClientDetailsModification(clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("scim.read","scim.invite"), Arrays.asList(new String[]{"client_credentials", "password"}), authorities, Collections.EMPTY_SET); + IdentityZone original = IdentityZoneHolder.get(); + try { + IdentityZoneHolder.set(zone); + getWebApplicationContext().getBean(MultitenantJdbcClientDetailsService.class).addClientDetails(client); + } finally { + IdentityZoneHolder.set(original); + } + String acceptInvitationLink = getAcceptInvitationLink(zone); + + getMockMvc().perform(get(acceptInvitationLink) + .header("Host",(subdomain + ".localhost"))) .andExpect(content().string(containsString("Create your account"))) .andExpect(content().string(not(containsString("Pivotal ID")))) .andExpect(content().string(not(containsString("Create Pivotal ID")))) @@ -191,7 +202,7 @@ public void accept_Invitation_Email_Within_Zone() throws Exception { public void invitations_Accept_Get_Security() throws Exception { getWebApplicationContext().getBean(JdbcTemplate.class).update("DELETE FROM expiring_code_store"); - String userToken = utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret); + String userToken = utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret, null); sendRequestWithToken(userToken, null, clientId, "example.com", "user1@"+domain); String code = getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("SELECT code FROM expiring_code_store", String.class); @@ -248,10 +259,10 @@ private void assertResponseAndCodeCorrect(String[] emails, String redirectUrl, S } } - private String getAcceptInvitationLink() throws Exception { - String userToken = utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret); + private String getAcceptInvitationLink(IdentityZone zone) throws Exception { + String userToken = utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret, zone); String email = generator.generate().toLowerCase() + "@"+domain; - InvitationsResponse response = sendRequestWithTokenAndReturnResponse(userToken, null, clientId, "example.com", email); + InvitationsResponse response = sendRequestWithTokenAndReturnResponse(userToken, zone==null?null:zone.getSubdomain(), clientId, "example.com", email); return response.getNewInvites().get(0).getInviteLink().toString(); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java index 0b553e6a1d3..d90c81b5283 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java @@ -109,61 +109,32 @@ public static void stopMailServer() throws Exception { @Test public void testCreateActivationEmailPage() throws Exception { - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "oss"); - getMockMvc().perform(get("/create_account")) - .andExpect(content().string(containsString("Create your account"))) - .andExpect(content().string(not(containsString("Pivotal ID")))); - } - - @Test - public void testCreateActivationEmailPageWithPivotalBrand() throws Exception { - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "pivotal"); - - getMockMvc().perform(get("/create_account")) - .andExpect(content().string(containsString("Create your Pivotal ID"))) - .andExpect(content().string(not(containsString("Create your account")))); + .andExpect(content().string(containsString("Create your account"))); } @Test public void testCreateActivationEmailPageWithinZone() throws Exception { String subdomain = generator.generate(); mockMvcUtils.createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext()); - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "pivotal"); getMockMvc().perform(get("/create_account") .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) - .andExpect(content().string(containsString("Create your account"))) - .andExpect(content().string(not(containsString("Pivotal ID")))); + .andExpect(content().string(containsString("Create your account"))); } @Test public void testActivationEmailSentPage() throws Exception { - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "oss"); - getMockMvc().perform(get("/accounts/email_sent")) .andExpect(status().isOk()) .andExpect(content().string(containsString("Create your account"))) - .andExpect(xpath("//input[@disabled='disabled']/@value").string("Email successfully sent")) - .andExpect(content().string(not(containsString("Pivotal ID")))); - } - - @Test - public void testActivationEmailSentPageWithPivotalBrand() throws Exception { - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "pivotal"); - - getMockMvc().perform(get("/accounts/email_sent")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("Create your Pivotal ID"))) - .andExpect(xpath("//input[@disabled='disabled']/@value").string("Email successfully sent")) - .andExpect(content().string(not(containsString("Create your account")))); + .andExpect(xpath("//input[@disabled='disabled']/@value").string("Email successfully sent")); } @Test public void testActivationEmailSentPageWithinZone() throws Exception { String subdomain = generator.generate(); mockMvcUtils.createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext()); - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "pivotal"); getMockMvc().perform(get("/accounts/email_sent") .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) @@ -174,35 +145,23 @@ public void testActivationEmailSentPageWithinZone() throws Exception { } @Test - public void testPageTitleWithOssBrand() throws Exception { - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "oss"); - + public void testPageTitle() throws Exception { getMockMvc().perform(get("/create_account")) .andExpect(content().string(containsString("Cloud Foundry"))); } - @Test - public void testPageTitleWithPivotalBrand() throws Exception { - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "pivotal"); - - getMockMvc().perform(get("/create_account")) - .andExpect(content().string(containsString("Pivotal"))); - } - @Test public void testPageTitleWithinZone() throws Exception { String subdomain = generator.generate(); IdentityZone zone = mockMvcUtils.createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext()); - ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("login.brand", "pivotal"); - getMockMvc().perform(get("/create_account") .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) .andExpect(content().string(containsString("" + zone.getName() + ""))); } @Test - public void testImageWithOssBrand() throws Exception { + public void testImage() throws Exception { ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("assetBaseUrl", "/resources/oss"); getMockMvc().perform(get("/create_account")) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index 44a19d03326..654017887f0 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -25,14 +25,19 @@ import org.cloudfoundry.identity.uaa.oauth.UaaTokenServices; import org.cloudfoundry.identity.uaa.oauth.UaaTokenStore; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.provider.saml.ZoneAwareMetadataGenerator; import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; +import org.cloudfoundry.identity.uaa.scim.ScimGroup; +import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.security.web.CorsFilter; +import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneResolvingFilter; @@ -81,6 +86,9 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -434,6 +442,15 @@ public void testDefaultInternalHostnamesAndNoDBSettings() throws Exception { } } + @Test + public void bootstrap_scim_groups_from_yaml() throws Exception { + context = getServletContext(null, "login.yml", "test/bootstrap/uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + ScimGroupProvisioning scimGroupProvisioning = context.getBean("scimGroupProvisioning", ScimGroupProvisioning.class); + List scimGroups = scimGroupProvisioning.retrieveAll(); + assertThat(scimGroups, PredicateMatcher.has(g -> g.getDisplayName().equals("pony") && "The magic of friendship".equals(g.getDescription()))); + assertThat(scimGroups, PredicateMatcher.has(g -> g.getDisplayName().equals("cat") && "The cat".equals(g.getDescription()))); + } + @Test public void testBootstrappedIdps_and_ExcludedClaims_and_CorsConfig() throws Exception { @@ -457,7 +474,14 @@ public void testBootstrappedIdps_and_ExcludedClaims_and_CorsConfig() throws Exce assertNotNull(providerProvisioning.retrieveByOrigin(def.getIdpEntityAlias(), IdentityZone.getUaa().getId())); } - assertNotNull(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZone.getUaa().getId())); + IdentityProvider ldapProvider = + providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZone.getUaa().getId()); + assertNotNull(ldapProvider); + assertEquals("Test LDAP Provider Description", ldapProvider.getConfig().getProviderDescription()); + + IdentityProvider samlProvider = + providerProvisioning.retrieveByOrigin("okta-local", IdentityZone.getUaa().getId()); + assertEquals("Test Okta Preview 1 Description", samlProvider.getConfig().getProviderDescription()); CorsFilter filter = context.getBean(CorsFilter.class); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java index 3b25cd1d84a..32d1ab3b9cc 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java @@ -85,7 +85,7 @@ public void setUp() throws Exception { clientSecret = generator.generate().toLowerCase(); authorities = "scim.read,scim.invite"; MockMvcUtils.utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("scim.read","scim.invite"), Arrays.asList(new String[]{"client_credentials", "password"}), authorities, Collections.singleton(REDIRECT_URI), IdentityZone.getUaa()); - userInviteToken = MockMvcUtils.utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret); + userInviteToken = MockMvcUtils.utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret, null); getWebApplicationContext().getBean(JdbcTemplate.class).update("delete from expiring_code_store"); } @@ -319,13 +319,12 @@ protected IdentityProvider createIdentityProvider(IdentityZoneCreationResult zon } protected SamlIdentityProviderDefinition getSamlIdentityProviderDefinition(IdentityZoneCreationResult zone, String entityID) { - return SamlIdentityProviderDefinition.Builder.get() + return new SamlIdentityProviderDefinition() .setMetaDataLocation(String.format(utils.IDP_META_DATA, entityID)) .setIdpEntityAlias(entityID) .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setLinkText("Test Saml Provider") - .setZoneId(zone.getIdentityZone().getId()) - .build(); + .setZoneId(zone.getIdentityZone().getId()); } public URL inviteUser(String email, String userInviteToken, String subdomain, String clientId, String expectedOrigin) throws Exception { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index 8e93205128a..e75f9ebfa99 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -77,6 +77,7 @@ import static java.util.Collections.EMPTY_LIST; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; +import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; @@ -169,19 +170,48 @@ public void testLogin_When_DisableInternalUserManagement_Is_True() throws Except } @Test - public void testCopyrightPivotal() throws Exception { - mockEnvironment.setProperty("login.brand", "pivotal"); + public void testDefaultLogo() throws Exception { + mockEnvironment.setProperty("assetBaseUrl", "//cdn.example.com/resources"); getMockMvc().perform(get("/login")) - .andExpect(content().string(containsString("Copyright © Pivotal Software, Inc."))); + .andExpect(content().string(containsString("url(//cdn.example.com/resources/images/logo.png)"))); } @Test - public void testCopyrightCloudFoundry() throws Exception { - mockEnvironment.setProperty("login.brand", "cloudfoundry"); + public void testCustomLogo() throws Exception { + mockEnvironment.setProperty("login.branding.productLogo","/bASe/64+"); getMockMvc().perform(get("/login")) - .andExpect(content().string(containsString("Copyright © CloudFoundry.org Foundation, Inc."))); + .andExpect(content().string(allOf(containsString("url(data:image/png;base64,/bASe/64+)"), not(containsString("url(/uaa/resources/oss/images/logo.png)"))))); + } + + private static final String cfCopyrightText = "Copyright © CloudFoundry.org Foundation, Inc."; + @Test + public void testDefaultFooter() throws Exception { + getMockMvc().perform(get("/login")) + .andExpect(content().string(containsString(cfCopyrightText))); + } + + @Test + public void testCustomizedFooter() throws Exception { + String customFooterText = "This text should be in the footer."; + mockEnvironment.setProperty("login.branding.footerLegalText", customFooterText); + + getMockMvc().perform(get("/login")) + .andExpect(content().string(allOf(containsString(customFooterText), not(containsString(cfCopyrightText))))); + } + + @Test + public void testFooterLinks() throws Exception { + Map footerLinks = new HashMap<>(); + footerLinks.put("Terms of Use", "/terms.html"); + footerLinks.put("Privacy", "/privacy"); + // Insanity + propertySource.setProperty("login.branding.footerLegalLinks", footerLinks); + + getMockMvc().perform(get("/login")).andExpect(content().string(containsString("\n" + + " Privacy\n" + + " — Terms of Use"))); } @Test @@ -616,13 +646,12 @@ public void testSamlLoginLinksShowActiveProviders() throws Exception { String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(metadata) .setIdpEntityAlias(activeAlias) .setLinkText("Active SAML Provider") .setShowSamlLink(true) - .setZoneId(identityZone.getId()) - .build(); + .setZoneId(identityZone.getId()); IdentityProvider activeIdentityProvider = new IdentityProvider(); activeIdentityProvider.setType(OriginKeys.SAML); activeIdentityProvider.setName("Active SAML Provider"); @@ -632,12 +661,11 @@ public void testSamlLoginLinksShowActiveProviders() throws Exception { mockMvcUtils.createIdpUsingWebRequest(getMockMvc(), identityZone.getId(), zoneAdminToken, activeIdentityProvider, status().isCreated()); metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition inactiveSamlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition inactiveSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(metadata) .setIdpEntityAlias(inactiveAlias) .setLinkText("You should not see me") - .setZoneId(identityZone.getId()) - .build(); + .setZoneId(identityZone.getId()); IdentityProvider inactiveIdentityProvider = new IdentityProvider(); inactiveIdentityProvider.setType(OriginKeys.SAML); inactiveIdentityProvider.setName("Inactive SAML Provider"); @@ -664,12 +692,11 @@ public void testSamlRedirectWhenTheOnlyProvider() throws Exception { String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(metadata) .setIdpEntityAlias(alias) .setLinkText("Active SAML Provider") - .setZoneId(identityZone.getId()) - .build(); + .setZoneId(identityZone.getId()); IdentityProvider activeIdentityProvider = new IdentityProvider(); activeIdentityProvider.setType(OriginKeys.SAML); activeIdentityProvider.setName("Active SAML Provider"); @@ -723,12 +750,11 @@ public void testNoCreateAccountLinksWhenUAAisNotAllowedProvider() throws Excepti IdentityZone identityZone = identityZoneCreationResult.getIdentityZone(); String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); - SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition3 = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition3 = new SamlIdentityProviderDefinition() .setMetaDataLocation(String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://example3.com/saml/metadata")) .setIdpEntityAlias(alias3) .setLinkText("Active3 SAML Provider") - .setZoneId(identityZone.getId()) - .build(); + .setZoneId(identityZone.getId()); IdentityProvider activeIdentityProvider3 = new IdentityProvider(); activeIdentityProvider3.setType(OriginKeys.SAML); activeIdentityProvider3.setName("Active 3 SAML Provider"); @@ -737,12 +763,11 @@ public void testNoCreateAccountLinksWhenUAAisNotAllowedProvider() throws Excepti activeIdentityProvider3.setOriginKey(alias3); activeIdentityProvider3 = mockMvcUtils.createIdpUsingWebRequest(getMockMvc(), identityZone.getId(), zoneAdminToken, activeIdentityProvider3, status().isCreated()); - SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition2 = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition2 = new SamlIdentityProviderDefinition() .setMetaDataLocation(String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://example2.com/saml/metadata")) .setIdpEntityAlias(alias2) .setLinkText("Active2 SAML Provider") - .setZoneId(identityZone.getId()) - .build(); + .setZoneId(identityZone.getId()); IdentityProvider activeIdentityProvider2 = new IdentityProvider(); activeIdentityProvider2.setType(OriginKeys.SAML); activeIdentityProvider2.setName("Active 2 SAML Provider"); @@ -798,13 +823,12 @@ public void testDeactivatedProviderIsRemovedFromSamlLoginLinks() throws Exceptio String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition samlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition samlIdentityProviderDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(metadata) .setIdpEntityAlias(alias) .setLinkText("SAML Provider") .setShowSamlLink(true) - .setZoneId(identityZone.getId()) - .build(); + .setZoneId(identityZone.getId()); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.SAML); identityProvider.setName("SAML Provider"); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index 74b177288f2..ae6929c6399 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -16,16 +16,16 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.oauth.UaaAuthorizationEndpoint; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; -import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.DisableIdTokenResponseTypeFilter; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.oauth.SignerProvider; +import org.cloudfoundry.identity.uaa.oauth.UaaAuthorizationEndpoint; import org.cloudfoundry.identity.uaa.oauth.UaaTokenServices; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroup; @@ -42,7 +42,6 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; @@ -92,12 +91,10 @@ import java.util.TreeSet; import java.util.UUID; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.contains; +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.isIn; import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.StringStartsWith.startsWith; @@ -495,7 +492,7 @@ public void test_Oauth_Authorize_API_Endpoint() throws Exception { String userScopes = ""; setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); - String cfAccessToken = MockMvcUtils.utils().getUserOAuthAccessToken( + String cfAccessToken = utils().getUserOAuthAccessToken( getMockMvc(), "cf", "", @@ -1878,6 +1875,123 @@ public void testGetClientCredentialsTokenForDefaultIdentityZone() throws Excepti assertNotNull(claims.get(ClaimConstants.AZP)); } + @Test + public void test_Revoke_Client_And_User_Tokens() throws Exception { + String adminToken = + utils().getClientCredentialsOAuthAccessToken( + getMockMvc(), + "admin", + "adminsecret", + null, + null + ); + + BaseClientDetails client = new BaseClientDetails( + new RandomValueStringGenerator().generate(), + "", + "openid", + "client_credentials,password", + "clients.read"); + client.setClientSecret("secret"); + + utils().createClient(getMockMvc(), adminToken, client); + + //this is the token we will revoke + String readClientsToken = + utils().getClientCredentialsOAuthAccessToken( + getMockMvc(), + client.getClientId(), + client.getClientSecret(), + null, + null + ); + + //ensure our token works + getMockMvc().perform( + get("/oauth/clients") + .header("Authorization", "Bearer "+readClientsToken) + ).andExpect(status().isOk()); + + //ensure we can't get to the endpoint without authentication + getMockMvc().perform( + get("/oauth/token/revoke/client/"+client.getClientId()) + ).andExpect(status().isUnauthorized()); + + //ensure we can't get to the endpoint without correct scope + getMockMvc().perform( + get("/oauth/token/revoke/client/"+client.getClientId()) + .header("Authorization", "Bearer "+readClientsToken) + ).andExpect(status().isForbidden()); + + //ensure that we have the correct error for invalid client id + getMockMvc().perform( + get("/oauth/token/revoke/client/notfound"+new RandomValueStringGenerator().generate()) + .header("Authorization", "Bearer "+adminToken) + ).andExpect(status().isNotFound()); + + //we revoke the tokens for that client + getMockMvc().perform( + get("/oauth/token/revoke/client/"+client.getClientId()) + .header("Authorization", "Bearer "+adminToken) + ).andExpect(status().isOk()); + + //we should fail attempting to use the token + getMockMvc().perform( + get("/oauth/clients") + .header("Authorization", "Bearer "+readClientsToken) + ) + .andExpect(status().isUnauthorized()) + .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); + + + ScimUser user = new ScimUser(null, + new RandomValueStringGenerator().generate(), + "Given Name", + "Family Name"); + user.setPrimaryEmail(user.getUserName()+"@test.org"); + user.setPassword("password"); + + user = utils().createUser(getMockMvc(), adminToken, user); + user.setPassword("password"); + + String userInfoToken = utils().getUserOAuthAccessToken( + getMockMvc(), + client.getClientId(), + client.getClientSecret(), + user.getUserName(), + user.getPassword(), + "openid" + ); + + //ensure our token works + getMockMvc().perform( + get("/userinfo") + .header("Authorization", "Bearer "+userInfoToken) + ).andExpect(status().isOk()); + + //we revoke the tokens for that user + getMockMvc().perform( + get("/oauth/token/revoke/user/"+user.getId()+"notfound") + .header("Authorization", "Bearer "+adminToken) + ).andExpect(status().isNotFound()); + + + //we revoke the tokens for that user + getMockMvc().perform( + get("/oauth/token/revoke/user/"+user.getId()) + .header("Authorization", "Bearer "+adminToken) + ).andExpect(status().isOk()); + + getMockMvc().perform( + get("/userinfo") + .header("Authorization", "Bearer "+userInfoToken) + ) + .andExpect(status().isUnauthorized()) + .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); + + + } + @Test public void testGetClientCredentials_WithAuthoritiesExcluded_ForDefaultIdentityZone() throws Exception { Set originalExclude = getWebApplicationContext().getBean(UaaTokenServices.class).getExcludedClaims(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index ad781cb6a67..6408c277088 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -17,22 +17,26 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.RandomStringUtils; -import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.invitations.InvitationsRequest; import org.cloudfoundry.identity.uaa.invitations.InvitationsResponse; +import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.test.TestClient.OAuthToken; @@ -40,14 +44,10 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; -import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Assert; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; @@ -229,14 +229,14 @@ public static InvitationsResponse sendRequestWithTokenAndReturnResponse(Applicat .header("Authorization", "Bearer " + token) .contentType(APPLICATION_JSON) .content(requestBody); - if (org.flywaydb.core.internal.util.StringUtils.hasText(subdomain)) { - post.with(new SetServerNameRequestPostProcessor(subdomain+".localhost")); + if (hasText(subdomain)) { + post.header("Host",(subdomain+".localhost")); } MvcResult result = mockMvc.perform( post ) - .andExpect(status().isOk()) - .andReturn(); + .andExpect(status().isOk()) + .andReturn(); return JsonUtils.readValue(result.getResponse().getContentAsString(), InvitationsResponse.class); } @@ -404,7 +404,7 @@ public IdentityZone createOtherIdentityZone(String subdomain, MockMvc mockMvc, ApplicationContext webApplicationContext) throws Exception { BaseClientDetails client = new BaseClientDetails("admin", null, null, "client_credentials", - "clients.admin,scim.read,scim.write,idps.write"); + "clients.admin,scim.read,scim.write,idps.write,uaa.admin"); client.setClientSecret("admin-secret"); return createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, client); @@ -509,9 +509,16 @@ public ScimUser createAdminForZone(MockMvc mockMvc, String accessToken, String s } public ScimGroup getGroup(MockMvc mockMvc, String accessToken, String displayName) throws Exception { + return getGroup(mockMvc, accessToken, displayName, null); + } + public ScimGroup getGroup(MockMvc mockMvc, String accessToken, String displayName, String subdomain) throws Exception { String filter = "displayName eq \""+displayName+"\""; + MockHttpServletRequestBuilder builder = get("/Groups"); + if (hasText(subdomain)) { + builder.header("Host", subdomain+".localhost"); + } SearchResults results = JsonUtils.readValue( - mockMvc.perform(get("/Groups") + mockMvc.perform(builder .header("Authorization", "Bearer " + accessToken) .contentType(APPLICATION_JSON) .param("filter", filter)) @@ -527,6 +534,23 @@ public ScimGroup getGroup(MockMvc mockMvc, String accessToken, String displayNam public ScimGroup createGroup(MockMvc mockMvc, String accessToken, ScimGroup group) throws Exception { return createGroup(mockMvc, accessToken, group, null); } + + public ScimGroup createGroup(MockMvc mockMvc, String accessToken, String subdomain, ScimGroup group) throws Exception { + MockHttpServletRequestBuilder post = post("/Groups") + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group)); + if (hasText(subdomain)) { + post.header("Host", subdomain+".localhost"); + } + return JsonUtils.readValue( + mockMvc.perform(post) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(), + ScimGroup.class); + } + + public ScimGroup createGroup(MockMvc mockMvc, String accessToken, ScimGroup group, String zoneId) throws Exception { MockHttpServletRequestBuilder post = post("/Groups") .header("Authorization", "Bearer " + accessToken) @@ -543,12 +567,18 @@ public ScimGroup createGroup(MockMvc mockMvc, String accessToken, ScimGroup grou } public ScimGroup updateGroup(MockMvc mockMvc, String accessToken, ScimGroup group) throws Exception { + return updateGroup(mockMvc, accessToken, group, null); + } + public ScimGroup updateGroup(MockMvc mockMvc, String accessToken, ScimGroup group, IdentityZone zone) throws Exception { + MockHttpServletRequestBuilder put = put("/Groups/" + group.getId()); + if (zone!=null) { + put.header("Host", zone.getSubdomain()+".localhost"); + } return JsonUtils.readValue( - mockMvc.perform(put("/Groups/" + group.getId()) - .header("If-Match", group.getVersion()) - .header("Authorization", "Bearer " + accessToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(group))) + mockMvc.perform(put.header("If-Match", group.getVersion()) + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group))) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), ScimGroup.class); @@ -579,6 +609,11 @@ public ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, Stri } public ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, String id, String secret, Collection resourceIds, Collection scopes, Collection grantTypes, String authorities, Set redirectUris, IdentityZone zone) throws Exception { + ClientDetailsModification client = getClientDetailsModification(id, secret, resourceIds, scopes, grantTypes, authorities, redirectUris); + return createClient(mockMvc,adminAccessToken, client, zone); + } + + public ClientDetailsModification getClientDetailsModification(String id, String secret, Collection resourceIds, Collection scopes, Collection grantTypes, String authorities, Set redirectUris) { ClientDetailsModification detailsModification = new ClientDetailsModification(); detailsModification.setClientId(id); detailsModification.setResourceIds(resourceIds); @@ -588,7 +623,7 @@ public ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, Stri detailsModification.setRegisteredRedirectUri(redirectUris); ClientDetailsModification client = detailsModification; client.setClientSecret(secret); - return createClient(mockMvc,adminAccessToken, client, zone); + return client; } public BaseClientDetails updateClient(MockMvc mockMvc, String accessToken, BaseClientDetails clientDetails, IdentityZone zone) @@ -651,18 +686,34 @@ public String getZoneAdminToken(MockMvc mockMvc, String adminToken, String zoneI } - public String getUserOAuthAccessToken(MockMvc mockMvc, String clientId, String clientSecret, String username, - String password, String scope) - throws Exception { + public String getUserOAuthAccessToken(MockMvc mockMvc, + String clientId, + String clientSecret, + String username, + String password, + String scope) throws Exception { + return getUserOAuthAccessToken(mockMvc, clientId, clientSecret, username, password, scope, null); + } + public String getUserOAuthAccessToken(MockMvc mockMvc, + String clientId, + String clientSecret, + String username, + String password, + String scope, + IdentityZone zone) throws Exception { String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64((clientId + ":" + clientSecret).getBytes())); - MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token") + MockHttpServletRequestBuilder oauthTokenPost = + post("/oauth/token") .header("Authorization", basicDigestHeaderValue) .param("grant_type", "password") .param("client_id", clientId) .param("username", username) .param("password", password) .param("scope", scope); + if (zone!=null) { + oauthTokenPost.header("Host", zone.getSubdomain()+".localhost"); + } MvcResult result = mockMvc.perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); TestClient.OAuthToken oauthToken = JsonUtils.readValue(result.getResponse().getContentAsString(), TestClient.OAuthToken.class); @@ -671,16 +722,7 @@ public String getUserOAuthAccessToken(MockMvc mockMvc, String clientId, String c public String getClientOAuthAccessToken(MockMvc mockMvc, String clientId, String clientSecret, String scope) throws Exception { - String basicDigestHeaderValue = "Basic " - + new String(Base64.encodeBase64((clientId + ":" + clientSecret).getBytes())); - MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token") - .header("Authorization", basicDigestHeaderValue) - .param("grant_type", "client_credentials") - .param("client_id", clientId) - .param("scope", scope); - MvcResult result = mockMvc.perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); - TestClient.OAuthToken oauthToken = JsonUtils.readValue(result.getResponse().getContentAsString(), TestClient.OAuthToken.class); - return oauthToken.accessToken; + return getClientCredentialsOAuthAccessToken(mockMvc, clientId, clientSecret, scope, null); } public String getUserOAuthAccessTokenAuthCode(MockMvc mockMvc, String clientId, String clientSecret, String userId, String username, String password, String scope) throws Exception { @@ -731,43 +773,65 @@ public String getUserOAuthAccessTokenAuthCode(MockMvc mockMvc, String clientId, } - public String getScimInviteUserToken(MockMvc mockMvc, String clientId, String clientSecret) throws Exception { - String adminToken = getClientCredentialsOAuthAccessToken(mockMvc, "admin", "adminsecret", "", null); + public String getScimInviteUserToken(MockMvc mockMvc, String clientId, String clientSecret, IdentityZone zone) throws Exception { + String adminToken = getClientCredentialsOAuthAccessToken(mockMvc, + "admin", + zone==null?"adminsecret":"admin-secret", + "", + zone==null?null:zone.getSubdomain() + ); // create a user (with the required permissions) to perform the actual /invite_users action String username = new RandomValueStringGenerator().generate().toLowerCase()+"@example.com"; ScimUser user = new ScimUser(clientId, username, "given-name", "family-name"); user.setPrimaryEmail(username); user.setPassword("password"); - user = createUser(mockMvc, adminToken, user); + user = (zone == null) ? createUser(mockMvc, adminToken, user) : createUserInZone(mockMvc,adminToken,user,zone.getSubdomain(), null); String scope = "scim.invite"; ScimGroupMember member = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.READER)); + ScimGroup inviteGroup = new ScimGroup(scope); - ScimGroup group = getGroup(mockMvc, adminToken, scope); + if (zone!=null) { + createGroup(mockMvc, adminToken, zone.getSubdomain(), inviteGroup); + } + ScimGroup group = getGroup(mockMvc, + adminToken, + scope, + zone==null?null:zone.getSubdomain() + ); group.getMembers().add(member); - updateGroup(mockMvc, adminToken, group); + updateGroup(mockMvc, adminToken, group, zone); user.getGroups().add(new ScimUser.Group(group.getId(), scope)); // get a bearer token for the user - return getUserOAuthAccessToken(mockMvc, clientId, clientSecret, user.getUserName(), "password", "scim.invite"); + return getUserOAuthAccessToken(mockMvc, + clientId, + clientSecret, + user.getUserName(), + "password", + "scim.invite", + zone + ); } - public String getClientCredentialsOAuthAccessToken(MockMvc mockMvc, String username, String password, String scope, - String subdomain) - throws Exception { + public String getClientCredentialsOAuthAccessToken(MockMvc mockMvc, + String clientId, + String clientSecret, + String scope, + String subdomain) throws Exception { String basicDigestHeaderValue = "Basic " - + new String(Base64.encodeBase64((username + ":" + password).getBytes())); + + new String(Base64.encodeBase64((clientId + ":" + clientSecret).getBytes())); MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token") .header("Authorization", basicDigestHeaderValue) .param("grant_type", "client_credentials") - .param("client_id", username) + .param("client_id", clientId) .param("scope", scope); if (subdomain != null && !subdomain.equals("")) oauthTokenPost.with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")); MvcResult result = mockMvc.perform(oauthTokenPost) - .andExpect(status().isOk()) - .andReturn(); + .andExpect(status().isOk()) + .andReturn(); OAuthToken oauthToken = JsonUtils.readValue(result.getResponse().getContentAsString(), OAuthToken.class); return oauthToken.accessToken; } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java index 47ce3b7ada4..ddca2d4b085 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java @@ -16,21 +16,21 @@ import org.apache.commons.lang.RandomStringUtils; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; -import org.cloudfoundry.identity.uaa.provider.saml.IdentityProviderConfiguratorTests; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.IdentityProviderConfiguratorTests; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; -import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.event.IdentityProviderModifiedEvent; import org.junit.After; import org.junit.Before; @@ -57,7 +57,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class IdentityProviderEndpointsMockMvcTests extends InjectedMockContextTest { @@ -110,10 +109,9 @@ public void test_Create_and_Delete_SamlProvider() throws Exception { provider.setIdentityZoneId(IdentityZone.getUaa().getId()); provider.setType(OriginKeys.SAML); provider.setOriginKey(origin); - SamlIdentityProviderDefinition samlDefinition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition samlDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(metadata) - .setLinkText("Test SAML Provider") - .build(); + .setLinkText("Test SAML Provider"); samlDefinition.setEmailDomain(Arrays.asList("test.com", "test2.com")); List externalGroupsWhitelist = new ArrayList<>(); externalGroupsWhitelist.add("value"); @@ -126,6 +124,7 @@ public void test_Create_and_Delete_SamlProvider() throws Exception { IdentityProvider created = createIdentityProvider(null, provider, accessToken, status().isCreated()); assertNotNull(created.getConfig()); + createIdentityProvider(null, created, accessToken, status().isConflict()); SamlIdentityProviderDefinition samlCreated = created.getConfig(); assertEquals(Arrays.asList("test.com", "test2.com"), samlCreated.getEmailDomain()); assertEquals(externalGroupsWhitelist, samlCreated.getExternalGroupsWhitelist()); @@ -317,13 +316,12 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Other_Zone() throws IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, zone.getId()); identityProvider.setType(OriginKeys.SAML); - SamlIdentityProviderDefinition providerDefinition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://www.okta.com/" + identityProvider.getOriginKey())) .setIdpEntityAlias(identityProvider.getOriginKey()) .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) - .setZoneId(zone.getId()) - .build(); + .setZoneId(zone.getId()); identityProvider.setConfig(providerDefinition); IdentityProvider createdIDP = createIdentityProvider(zone.getId(), identityProvider, userAccessToken, status().isCreated()); @@ -335,13 +333,12 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Other_Zone() throws assertEquals(identityProvider.getConfig().getZoneId(), createdIDP.getConfig().getZoneId()); identityProvider.setOriginKey(origin2); - providerDefinition = SamlIdentityProviderDefinition.Builder.get() + providerDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(providerDefinition.getMetaDataLocation()) .setIdpEntityAlias(identityProvider.getOriginKey()) .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) - .setZoneId(zone.getId()) - .build(); + .setZoneId(zone.getId()); identityProvider.setConfig(providerDefinition); createIdentityProvider(zone.getId(), identityProvider, userAccessToken, status().isConflict()); @@ -360,13 +357,12 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Default_Zone() throw IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, IdentityZone.getUaa().getId()); identityProvider.setType(OriginKeys.SAML); - SamlIdentityProviderDefinition providerDefinition = SamlIdentityProviderDefinition.Builder.get() + SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://www.okta.com/" + identityProvider.getOriginKey())) .setIdpEntityAlias(identityProvider.getOriginKey()) .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) - .setZoneId(IdentityZone.getUaa().getId()) - .build(); + .setZoneId(IdentityZone.getUaa().getId()); identityProvider.setConfig(providerDefinition); IdentityProvider createdIDP = createIdentityProvider(null, identityProvider, userAccessToken, status().isCreated()); @@ -376,13 +372,12 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Default_Zone() throw assertEquals(identityProvider.getOriginKey(), createdIDP.getOriginKey()); identityProvider.setOriginKey(origin2); - providerDefinition = SamlIdentityProviderDefinition.Builder.get() + providerDefinition = new SamlIdentityProviderDefinition() .setMetaDataLocation(providerDefinition.getMetaDataLocation()) .setIdpEntityAlias(identityProvider.getOriginKey()) .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) - .setZoneId(IdentityZone.getUaa().getId()) - .build(); + .setZoneId(IdentityZone.getUaa().getId()); identityProvider.setConfig(providerDefinition); createIdentityProvider(null, identityProvider, userAccessToken, status().isConflict()); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index c0ee78a1697..4484bc762a8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -520,11 +520,11 @@ public void test_delete_zone_cleans_db() throws Exception { .setIdentityZoneId(zone.getId()) .setName("Delete Test") .setType(LOGIN_SERVER); + IdentityZoneHolder.set(zone); provider = idpp.create(provider); assertNotNull(idpp.retrieveByOrigin(LOGIN_SERVER, zone.getId())); assertEquals(provider.getId(), idpp.retrieveByOrigin(LOGIN_SERVER, zone.getId()).getId()); - IdentityZoneHolder.set(zone); //create user and add user to group ScimUser user = getScimUser(); user.setOrigin(LOGIN_SERVER); @@ -551,7 +551,6 @@ public void test_delete_zone_cleans_db() throws Exception { .param("username", user.getUserName()) .param("password", "adasda") ) - .andDo(print()) .andExpect(status().isFound()); //ensure we have some audit records diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java index aac84c258e9..e0f10de0840 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java @@ -215,7 +215,6 @@ public void configureProvider() throws Exception { when(consumer.processAuthenticationResponse(anyObject())).thenReturn(credential); userDatabase = new JdbcUaaUserDatabase(jdbcTemplate); - userDatabase.setUserAuthoritiesQuery("select g.displayName from groups g, group_membership m where g.id = m.group_id and m.member_id = ?"); userDatabase.setDefaultAuthorities(new HashSet<>(Arrays.asList(UaaAuthority.UAA_USER.getAuthority()))); providerProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); publisher = new CreateUserPublisher(bootstrap); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index 3696105a69a..854a8615545 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -62,13 +62,13 @@ import java.util.Set; import java.util.stream.Collectors; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -318,7 +318,7 @@ public void testGroupOperations_as_Zone_Admin() throws Exception { .header("Authorization", "bearer " + zoneAdminToken) .accept(APPLICATION_JSON); - Assert.assertEquals(group, JsonUtils.readValue( + assertEquals(group, JsonUtils.readValue( getMockMvc().perform(get) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), @@ -617,6 +617,60 @@ public void testCreateExternalGroupMapUsingId() throws Exception { checkGetExternalGroups(); } + @Test + public void test_create_and_update_group_description() throws Exception { + String name = new RandomValueStringGenerator().generate(); + ScimGroup group = new ScimGroup(name); + group.setZoneId("some-other-zone"); + group.setDescription(name+"-description"); + + String content = JsonUtils.writeValueAsString(group); + MockHttpServletRequestBuilder action = MockMvcRequestBuilders.post("/Groups") + .header("Authorization", "Bearer " + scimWriteToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(content); + + ScimGroup newGroup = + JsonUtils.readValue( + getMockMvc().perform(action) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(), + ScimGroup.class + ); + assertNotNull(newGroup); + assertNotNull(newGroup.getId()); + assertEquals(IdentityZone.getUaa().getId(), newGroup.getZoneId()); + assertEquals(group.getDisplayName(), newGroup.getDisplayName()); + assertEquals(group.getDescription(), newGroup.getDescription()); + + group.setDescription(name+"-description-updated"); + newGroup.setDescription(group.getDescription()); + + content = JsonUtils.writeValueAsString(newGroup); + action = MockMvcRequestBuilders.put("/Groups/"+newGroup.getId()) + .header("Authorization", "Bearer " + scimWriteToken) + .header("If-Match", newGroup.getVersion()) + .contentType(MediaType.APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(content); + + newGroup = + JsonUtils.readValue( + getMockMvc().perform(action) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + ScimGroup.class + ); + + assertNotNull(newGroup); + assertNotNull(newGroup.getId()); + assertEquals(IdentityZone.getUaa().getId(), newGroup.getZoneId()); + assertEquals(group.getDisplayName(), newGroup.getDisplayName()); + assertEquals(group.getDescription(), newGroup.getDescription()); + + } + protected ResultActions createGroup(String id, String name, String externalName) throws Exception { ScimGroupExternalMember em = new ScimGroupExternalMember(); if (id!=null) em.setGroupId(id); @@ -795,7 +849,7 @@ public void get_group_membership() throws Exception { .andExpect(status().isOk()) .andReturn(); ScimGroupMember scimGroupMember = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), ScimGroupMember.class); - Assert.assertNotNull(scimGroupMember); + assertNotNull(scimGroupMember); assertEquals(scimUser.getId(), scimGroupMember.getMemberId()); } @@ -906,7 +960,7 @@ public void update_member_in_group() throws Exception { .header("Content-Type", APPLICATION_JSON_VALUE) .content(updatedMember)) .andExpect(status().isOk()); - Assert.assertNotNull(updatedMember); + assertNotNull(updatedMember); MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/" + scimGroupMember.getMemberId()) .header("Authorization", "Bearer " + scimReadToken); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java index 5f395839f44..d75c1520d25 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java @@ -29,13 +29,13 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.hamcrest.MatcherAssert; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; @@ -43,7 +43,6 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.request.RequestPostProcessor; import java.nio.charset.Charset; import java.util.Arrays; @@ -53,16 +52,13 @@ import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertThat; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID; import static org.springframework.security.oauth2.common.util.OAuth2Utils.REDIRECT_URI; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -161,7 +157,6 @@ public void test_Create_User_Too_Long_Password() throws Exception { user.setPassword(new RandomValueStringGenerator(300).generate()); ResultActions result = createUserAndReturnResult(user, scimReadWriteToken, null, null); result.andExpect(status().isBadRequest()) - .andDo(print()) .andExpect(jsonPath("$.error").value("invalid_password")) .andExpect(jsonPath("$.message").value("Password must be no more than 255 characters in length.")) .andExpect(jsonPath("$.error_description").value("Password must be no more than 255 characters in length.")); @@ -213,17 +208,10 @@ public void verification_link_in_non_default_zone() throws Exception { zonedClientDetails.setClientSecret(zonedClientSecret); String zonedScimCreateToken = utils().getClientCredentialsOAuthAccessToken(getMockMvc(), zonedClientDetails.getClientId(), zonedClientDetails.getClientSecret(), "scim.create", subdomain); - ScimUser joel = setUpScimUser(); + ScimUser joel = setUpScimUser(zoneResult.getIdentityZone()); MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users/" + joel.getId() + "/verify-link") - .with(new RequestPostProcessor() { - - @Override - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - request.setServerName(subdomain + ".localhost"); - return request; - } - }) + .header("Host", subdomain + ".localhost") .header("Authorization", "Bearer " + zonedScimCreateToken) .param("redirect_uri", HTTP_REDIRECT_EXAMPLE_COM) .accept(APPLICATION_JSON); @@ -557,12 +545,22 @@ private MockHttpServletRequestBuilder setUpVerificationLinkRequest(ScimUser user } private ScimUser setUpScimUser() { - String email = "joe@"+generator.generate().toLowerCase()+".com"; - ScimUser joel = new ScimUser(null, email, "Joel", "D'sa"); - joel.setVerified(false); - joel.addEmail(email); - joel = usersRepository.createUser(joel, "pas5Word"); - return joel; + return setUpScimUser(IdentityZoneHolder.get()); + } + + private ScimUser setUpScimUser(IdentityZone zone) { + IdentityZone original = IdentityZoneHolder.get(); + try { + IdentityZoneHolder.set(zone); + String email = "joe@" + generator.generate().toLowerCase() + ".com"; + ScimUser joel = new ScimUser(null, email, "Joel", "D'sa"); + joel.setVerified(false); + joel.addEmail(email); + joel = usersRepository.createUser(joel, "pas5Word"); + return joel; + } finally { + IdentityZoneHolder.set(original); + } } private String getQueryStringParam(String query, String key) { diff --git a/uaa/src/test/resources/test/bootstrap/login.yml b/uaa/src/test/resources/test/bootstrap/login.yml index 0374c29820f..5fa0aabb5a8 100644 --- a/uaa/src/test/resources/test/bootstrap/login.yml +++ b/uaa/src/test/resources/test/bootstrap/login.yml @@ -36,6 +36,7 @@ login: showSamlLoginLink: true linkText: 'Okta Preview 1' iconUrl: 'http://link.to/icon.jpg' + providerDescription: 'Test Okta Preview 1 Description' okta-local-2: idpMetadata: | MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG diff --git a/uaa/src/test/resources/test/bootstrap/uaa.yml b/uaa/src/test/resources/test/bootstrap/uaa.yml index 303006ef50b..5d27575aed1 100644 --- a/uaa/src/test/resources/test/bootstrap/uaa.yml +++ b/uaa/src/test/resources/test/bootstrap/uaa.yml @@ -7,6 +7,7 @@ ldap: password: 'password' searchBase: '' searchFilter: 'cn={0}' + providerDescription: 'Test LDAP Provider Description' jwt: token: claims: @@ -37,3 +38,5 @@ cors: - POST - PUT default: *xhr +scim: + groups: pony|The magic of friendship,cat|The cat