Repository: gravitee-io/graviteeio-access-management
Pull request #544 (2019-03-12 10:57:11)

feat(users): allow to select an idp for user creation
closes gravitee-io/issues#2018


Patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/scim/impl/UserServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/scim/impl/UserServiceImpl.java
index bd1106e56f..1aa3eab1b9 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/scim/impl/UserServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/scim/impl/UserServiceImpl.java
@@ -101,8 +101,10 @@ public class UserServiceImpl implements UserService {
     public Single<User> create(User user, String baseUrl) {
         LOGGER.debug("Create a new user {} for domain {}", user.getUserName(), domain.getName());
 
+        // set user idp source
+        final String source = user.getSource() == null ? DEFAULT_IDP_PREFIX + domain.getId() : user.getSource();
+
         // check if user is unique
-        final String source = DEFAULT_IDP_PREFIX + domain.getId();
         return userRepository.findByDomainAndUsernameAndSource(domain.getId(), user.getUserName(), source)
                 .isEmpty()
                 .map(isEmpty -> {
@@ -111,7 +113,9 @@ public class UserServiceImpl implements UserService {
                     }
                     return true;
                 })
-                .flatMap(irrelevant -> {
+                .flatMapMaybe(irrelevant -> identityProviderManager.getUserProvider(source))
+                .switchIfEmpty(Maybe.error(new UserProviderNotFoundException(source)))
+                .flatMapSingle(userProvider -> {
                     io.gravitee.am.model.User userModel = convert(user);
                     // set technical ID
                     userModel.setId(RandomString.generate());
@@ -125,9 +129,7 @@ public class UserServiceImpl implements UserService {
                         return userRepository.create(userModel);
                     } else {
                         // store user in its identity provider
-                        return identityProviderManager.getUserProvider(userModel.getSource())
-                                .switchIfEmpty(Maybe.error(new UserProviderNotFoundException(userModel.getSource())))
-                                .flatMapSingle(userProvider -> userProvider.create(convert(userModel)))
+                        return userProvider.create(convert(userModel))
                                 .flatMap(idpUser -> {
                                     // AM 'users' collection is not made for authentication (but only management stuff)
                                     // clear password
@@ -140,7 +142,7 @@ public class UserServiceImpl implements UserService {
                 })
                 .map(user1 -> convert(user1, baseUrl, true))
                 .onErrorResumeNext(ex -> {
-                    if (ex instanceof SCIMException) {
+                    if (ex instanceof SCIMException || ex instanceof UserProviderNotFoundException) {
                         return Single.error(ex);
                     } else {
                         LOGGER.error("An error occurs while trying to create a user", ex);
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/scim/model/User.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/scim/model/User.java
index 56abe2b1b6..0071b00e62 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/scim/model/User.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/scim/model/User.java
@@ -279,6 +279,11 @@ public class User extends Resource {
      */
     private List<Certificate> x509Certificates;
 
+    /**
+     * Extended custom AM source field. Represents in which idp the user will be stored.
+     */
+    private String source;
+
     public String getUserName() {
         return userName;
     }
@@ -446,4 +451,12 @@ public class User extends Resource {
     public void setX509Certificates(List<Certificate> x509Certificates) {
         this.x509Certificates = x509Certificates;
     }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/impl/UserServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/impl/UserServiceImpl.java
index 457ec46d61..dfb3f8d488 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/impl/UserServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/user/impl/UserServiceImpl.java
@@ -98,8 +98,10 @@ public class UserServiceImpl implements UserService {
 
     @Override
     public Single<User> register(User user) {
-        final String source = DEFAULT_IDP_PREFIX + domain.getId();
+        // set user idp source
+        final String source = user.getSource() == null ? DEFAULT_IDP_PREFIX + domain.getId() : user.getSource();
 
+        // check user uniqueness
         return userRepository.findByDomainAndUsernameAndSource(domain.getId(), user.getUsername(), source)
                 .isEmpty()
                 .map(isEmpty -> {
@@ -108,6 +110,7 @@ public class UserServiceImpl implements UserService {
                     }
                     return true;
                 })
+                // check if user provider exists
                 .flatMap(irrelevant -> identityProviderManager.getUserProvider(source)
                         .switchIfEmpty(Maybe.error(new UserProviderNotFoundException(source)))
                         .flatMapSingle(userProvider -> userProvider.create(convert(user)))
@@ -134,6 +137,7 @@ public class UserServiceImpl implements UserService {
     public Completable confirmRegistration(User user) {
         // user has completed his account, add it to the idp
         return identityProviderManager.getUserProvider(user.getSource())
+                .switchIfEmpty(Maybe.error(new UserProviderNotFoundException(user.getSource())))
                 .flatMapSingle(userProvider -> userProvider.create(convert(user)))
                 .flatMap(idpUser -> {
                     // update 'users' collection for management and audit purpose
@@ -151,6 +155,7 @@ public class UserServiceImpl implements UserService {
     public Completable resetPassword(User user) {
         // only idp manage password, find user idp and update its password
         return identityProviderManager.getUserProvider(user.getSource())
+                .switchIfEmpty(Maybe.error(new UserProviderNotFoundException(user.getSource())))
                 .flatMapSingle(userProvider -> userProvider.update(user.getExternalId(), convert(user)))
                 .flatMap(idpUser -> {
                     // update 'users' collection for management and audit purpose
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/IdentityProvidersResource.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/IdentityProvidersResource.java
index 790db6c7ee..5b161ff6e1 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/IdentityProvidersResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/IdentityProvidersResource.java
@@ -23,6 +23,7 @@ import io.gravitee.am.service.exception.DomainNotFoundException;
 import io.gravitee.am.service.model.NewIdentityProvider;
 import io.gravitee.common.http.MediaType;
 import io.reactivex.Maybe;
+import io.reactivex.Observable;
 import io.swagger.annotations.*;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -65,17 +66,25 @@ public class IdentityProvidersResource extends AbstractResource {
             @ApiResponse(code = 200, message = "List registered identity providers for a security domain", response = IdentityProvider.class, responseContainer = "Set"),
             @ApiResponse(code = 500, message = "Internal server error")})
     public void list(@PathParam("domain") String domain,
+                     @QueryParam("userProvider") boolean userProvider,
                      @Suspended final AsyncResponse response) {
         domainService.findById(domain)
                 .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
-                .flatMapSingle(irrelevant -> identityProviderService.findByDomain(domain)
-                        .map(identities -> {
-                            List<IdentityProvider> sortedIdentityProviders = identities.stream()
-                                    .sorted((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName()))
-                                    .collect(Collectors.toList());
-                            return Response.ok(sortedIdentityProviders).build();
-                        })
-                )
+                .flatMapSingle(irrelevant -> identityProviderService.findByDomain(domain))
+                .flatMapObservable(identities -> Observable.fromIterable(identities))
+                .filter(identityProvider -> {
+                    if (userProvider) {
+                        return identityProviderManager.userProviderExists(identityProvider.getId());
+                    }
+                    return true;
+                })
+                .toList()
+                .map(identities -> {
+                    List<IdentityProvider> sortedIdentityProviders = identities.stream()
+                            .sorted((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName()))
+                            .collect(Collectors.toList());
+                    return Response.ok(sortedIdentityProviders).build();
+                })
                 .subscribe(
                         result -> response.resume(result),
                         error -> response.resume(error));
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/IdentityProviderManager.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/IdentityProviderManager.java
index 27408bc92e..cae7f83536 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/IdentityProviderManager.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/IdentityProviderManager.java
@@ -31,4 +31,6 @@ public interface IdentityProviderManager {
     Maybe<UserProvider> getUserProvider(String userProvider);
 
     Single<IdentityProvider> create(String domain);
+
+    boolean userProviderExists(String identityProviderId);
 }
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java
index 959e73d321..e63f513b95 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java
@@ -97,6 +97,11 @@ public class IdentityProviderManagerImpl implements IdentityProviderManager {
                 .flatMap(identityProvider -> reloadUserProvider(identityProvider));
     }
 
+    @Override
+    public boolean userProviderExists(String identityProviderId) {
+        return userProviders.containsKey(identityProviderId);
+    }
+
     private void loadUserProvider(IdentityProvider identityProvider) {
         UserProvider userProvider = identityProviderPluginManager.create(identityProvider.getType(), identityProvider.getConfiguration());
         if (userProvider != null) {
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java
index 8901a4dbbd..69c50755c9 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java
@@ -99,12 +99,17 @@ public class UserServiceImpl implements UserService {
 
     @Override
     public Single<User> create(String domain, NewUser newUser) {
-        return Single.just(newUser.isPreRegistration())
-                .flatMap(isPreRegistration -> {
-                    // set source (currently default idp)
-                    newUser.setSource(DEFAULT_IDP_PREFIX + domain);
+        // set user idp source
+        if (newUser.getSource() == null) {
+            newUser.setSource(DEFAULT_IDP_PREFIX + domain);
+        }
+
+        return identityProviderManager.getUserProvider(newUser.getSource())
+                .switchIfEmpty(Maybe.error(new UserProviderNotFoundException(newUser.getSource())))
+                .flatMapSingle(userProvider -> {
+                    // user is flagged as internal user
                     newUser.setInternal(true);
-                    if (isPreRegistration) {
+                    if (newUser.isPreRegistration()) {
                         // in pre registration mode an email will be sent to the user to complete his account
                         // and user will only be stored as 'readonly' account
                         newUser.setPassword(null);
@@ -117,17 +122,14 @@ public class UserServiceImpl implements UserService {
                         newUser.setEnabled(true);
                         newUser.setDomain(domain);
                         // store user in its identity provider
-                        return identityProviderManager.getUserProvider(newUser.getSource())
-                                .switchIfEmpty(Maybe.error(new UserProviderNotFoundException(newUser.getSource())))
-                                .flatMapSingle(userProvider -> userProvider.create(convert(newUser))
-                                        .onErrorResumeNext(ex -> {
-                                            if (ex instanceof UserAlreadyExistsException) {
-                                                return userProvider.findByUsername(newUser.getUsername()).toSingle();
-                                            } else {
-                                                return Single.error(ex);
-                                            }
-                                        })
-                                )
+                        return userProvider.create(convert(newUser))
+                                .onErrorResumeNext(ex -> {
+                                    if (ex instanceof UserAlreadyExistsException) {
+                                        return userProvider.findByUsername(newUser.getUsername()).toSingle();
+                                    } else {
+                                        return Single.error(ex);
+                                    }
+                                })
                                 .flatMap(idpUser -> {
                                     // AM 'users' collection is not made for authentication (but only management stuff)
                                     // clear password
diff --git a/gravitee-am-ui/src/app/domain/settings/users/creation/user-creation.component.html b/gravitee-am-ui/src/app/domain/settings/users/creation/user-creation.component.html
index f9fb3f025b..f09815f111 100644
--- a/gravitee-am-ui/src/app/domain/settings/users/creation/user-creation.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/users/creation/user-creation.component.html
@@ -42,6 +42,7 @@
       <div fxLayout="row">
         <mat-form-field style="margin-right: 20px;">
           <input matInput placeholder="Username" #username="ngModel" name="username" [(ngModel)]="user.username" [disabled]="useEmailAsUsername" required>
+          <mat-hint>Username for the User.</mat-hint>
           <mat-error *ngIf="username.errors?.required">Enter username</mat-error>
         </mat-form-field>
         <mat-checkbox [checked]="useEmailAsUsername" (change)="toggleUseEmailAsUsername($event)" [disabled]="email.invalid" style="margin-top: 20px;"><mat-hint style="font-size: 75%;">Use email as username</mat-hint></mat-checkbox>
@@ -63,6 +64,15 @@
       <div>
         <app-select-clients (onSelectClient)="onClientSelectionChanged($event)" (onRemoveClient)="onClientDeleted($event)"></app-select-clients>
       </div>
+      <div>
+        <mat-form-field>
+          <mat-select placeholder="Identity provider" [(ngModel)]="user.source" name="source">
+            <mat-option>None</mat-option>
+            <mat-option *ngFor="let userProvider of userProviders" [value]="userProvider.id">{{userProvider.name}}</mat-option>
+          </mat-select>
+          <mat-hint>Assign user to an identity provider. The user will be stored in the selected identity provider instead of the default one.</mat-hint>
+        </mat-form-field>
+      </div>
       <div fxLayout="column" class="additional-information">
         <h4>Additional information</h4>
         <span>Custom information about the user. These details will be available in the user's profile.</span>
diff --git a/gravitee-am-ui/src/app/domain/settings/users/creation/user-creation.component.ts b/gravitee-am-ui/src/app/domain/settings/users/creation/user-creation.component.ts
index 13aae4c088..7002992350 100644
--- a/gravitee-am-ui/src/app/domain/settings/users/creation/user-creation.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/users/creation/user-creation.component.ts
@@ -23,6 +23,7 @@ import {UserClaimComponent} from "./user-claim.component";
 import * as _ from 'lodash';
 import {FormControl} from "@angular/forms";
 import {ClientService} from "../../../../services/client.service";
+import {ProviderService} from "../../../../services/provider.service";
 
 @Component({
   selector: 'user-creation',
@@ -49,6 +50,7 @@ export class UserCreationComponent implements OnInit {
   clientCtrl = new FormControl();
   filteredClients: any[];
   selectedClient: any;
+  userProviders: any[];
   @ViewChild('dynamic', { read: ViewContainerRef }) viewContainerRef: ViewContainerRef;
 
   constructor(private userService: UserService,
@@ -56,7 +58,8 @@ export class UserCreationComponent implements OnInit {
               private route: ActivatedRoute,
               private snackbarService: SnackbarService,
               private factoryResolver: ComponentFactoryResolver,
-              private clientService: ClientService) {
+              private clientService: ClientService,
+              private providerService: ProviderService) {
   }
 
   ngOnInit() {
@@ -66,6 +69,10 @@ export class UserCreationComponent implements OnInit {
       this.adminContext = true;
     }
 
+    this.providerService.findUserProvidersByDomain(this.domainId).map(res => res.json()).subscribe(response => {
+      this.userProviders = response;
+    });
+
     this.clientCtrl.valueChanges
       .subscribe(searchTerm => {
         this.clientService.findByDomain(this.domainId).map(res => res.json()).subscribe(response => {
diff --git a/gravitee-am-ui/src/app/services/provider.service.ts b/gravitee-am-ui/src/app/services/provider.service.ts
index abff04478e..11959b8855 100644
--- a/gravitee-am-ui/src/app/services/provider.service.ts
+++ b/gravitee-am-ui/src/app/services/provider.service.ts
@@ -28,6 +28,10 @@ export class ProviderService {
     return this.http.get(this.providersURL + domainId + "/identities");
   }
 
+  findUserProvidersByDomain(domainId): Observable<Response> {
+    return this.http.get(this.providersURL + domainId + "/identities?userProvider=true");
+  }
+
   get(domainId, id): Observable<Response>  {
     return this.http.get(this.providersURL + domainId + "/identities/" + id);
   }


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/scim/UserServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/scim/UserServiceTest.java
new file mode 100644
index 0000000000..db979a6c7d
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/scim/UserServiceTest.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.gateway.handler.scim;
+
+import io.gravitee.am.gateway.handler.auth.idp.IdentityProviderManager;
+import io.gravitee.am.gateway.handler.scim.impl.UserServiceImpl;
+import io.gravitee.am.gateway.handler.scim.model.User;
+import io.gravitee.am.model.Domain;
+import io.gravitee.am.repository.management.api.UserRepository;
+import io.gravitee.am.service.exception.UserProviderNotFoundException;
+import io.reactivex.Maybe;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class UserServiceTest {
+
+    @InjectMocks
+    private UserService userService = new UserServiceImpl();
+
+    @Mock
+    private UserRepository userRepository;
+
+    @Mock
+    private IdentityProviderManager identityProviderManager;
+
+    @Mock
+    private Domain domain;
+
+    @Test
+    public void shouldCreateUser_invalid_identity_provider() {
+        final String domainId = "domain";
+
+        User newUser = mock(User.class);
+        when(newUser.getSource()).thenReturn("unknown-idp");
+
+        when(domain.getId()).thenReturn(domainId);
+        when(userRepository.findByDomainAndUsernameAndSource(anyString(), anyString(), anyString())).thenReturn(Maybe.empty());
+        when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.empty());
+
+        TestObserver<User> testObserver = userService.create(newUser, "/").test();
+        testObserver.assertNotComplete();
+        testObserver.assertError(UserProviderNotFoundException.class);
+    }
+
+
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/handler/scim/endpoint/users/CreateUserEndpointHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/handler/scim/endpoint/users/CreateUserEndpointHandlerTest.java
index b34246b96c..a08ea13363 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/handler/scim/endpoint/users/CreateUserEndpointHandlerTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/handler/scim/endpoint/users/CreateUserEndpointHandlerTest.java
@@ -23,6 +23,7 @@ import io.gravitee.am.gateway.handler.scim.model.User;
 import io.gravitee.am.gateway.handler.vertx.RxWebTestBase;
 import io.gravitee.am.gateway.handler.vertx.handler.scim.handler.ErrorHandler;
 import io.gravitee.am.service.authentication.crypto.password.PasswordValidator;
+import io.gravitee.am.service.exception.UserProviderNotFoundException;
 import io.reactivex.Single;
 import io.vertx.core.http.HttpMethod;
 import io.vertx.core.json.Json;
@@ -115,6 +116,31 @@ public class CreateUserEndpointHandlerTest extends RxWebTestBase {
                 "Created", null);
     }
 
+    @Test
+    public void shouldNotInvokeSCIMCreateUserEndpoint_invalid_identity_provider() throws Exception {
+        User user = getUser();
+        user.setSource("unknown-idp");
+
+        router.route("/Users").handler(createUserEndpointHandler);
+        when(passwordValidator.validate(anyString())).thenReturn(true);
+        when(userService.create(any(), any())).thenReturn(Single.error(new UserProviderNotFoundException(user.getSource())));
+
+        testRequest(
+                HttpMethod.POST,
+                "/Users",
+                req -> {
+                    req.setChunked(true);
+                    req.write(Json.encode(user));
+                },
+                404,
+                "Not Found",
+                "{\n" +
+                        "  \"status\" : \"404\",\n" +
+                        "  \"detail\" : \"User provider [unknown-idp] can not be found.\",\n" +
+                        "  \"schemas\" : [ \"urn:ietf:params:scim:api:messages:2.0:Error\" ]\n" +
+                        "}");
+    }
+
     private User getUser() {
         User user = new User();
         user.setUserName("username");
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/UsersResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/UsersResourceTest.java
index 47468f6b86..202b3f0677 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/UsersResourceTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/test/java/io/gravitee/am/management/handlers/management/api/resources/UsersResourceTest.java
@@ -20,6 +20,7 @@ import io.gravitee.am.model.Domain;
 import io.gravitee.am.model.User;
 import io.gravitee.am.model.common.Page;
 import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.exception.UserProviderNotFoundException;
 import io.gravitee.am.service.model.NewUser;
 import io.gravitee.common.http.HttpStatusCode;
 import io.reactivex.Maybe;
@@ -107,7 +108,29 @@ public class UsersResourceTest extends JerseySpringTest {
     }
 
     @Test
-    public void shouldNotCreate() {
+    public void shouldNotCreate_invalid_identity_provider() {
+        final String domainId = "domain-1";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        NewUser newUser = new NewUser();
+        newUser.setUsername("username");
+        newUser.setPassword("password");
+        newUser.setEmail("test@test.com");
+        newUser.setSource("unknown-source");
+
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Single.error(new UserProviderNotFoundException(newUser.getSource()))).when(userService).create(anyString(), any());
+
+        final Response response = target("domains")
+                .path(domainId)
+                .path("users")
+                .request().post(Entity.json(newUser));
+        assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
+    }
+
+    @Test
+    public void shouldCreate() {
         final String domainId = "domain-1";
         final Domain mockDomain = new Domain();
         mockDomain.setId(domainId);
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/UserServiceTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/UserServiceTest.java
new file mode 100644
index 0000000000..6709871a7b
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/UserServiceTest.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.management.service;
+
+import io.gravitee.am.management.service.impl.UserServiceImpl;
+import io.gravitee.am.model.User;
+import io.gravitee.am.service.exception.UserProviderNotFoundException;
+import io.gravitee.am.service.model.NewUser;
+import io.reactivex.Maybe;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class UserServiceTest {
+
+    @InjectMocks
+    private UserService userService = new UserServiceImpl();
+
+    @Mock
+    private IdentityProviderManager identityProviderManager;
+
+    @Test
+    public void shouldCreateUser_invalid_identity_provider() {
+        final String domain = "domain";
+
+        NewUser newUser = mock(NewUser.class);
+        when(newUser.getSource()).thenReturn("unknown-idp");
+
+        when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.empty());
+
+        TestObserver<User> testObserver = userService.create(domain, newUser).test();
+        testObserver.assertNotComplete();
+        testObserver.assertError(UserProviderNotFoundException.class);
+    }
+}