mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Ldap fixes (#3694)
* pass domainid for list users
* passing arg in wizzard
* adding userfilter to list ldap users and usersource to response
port of list ldap users tests to java
* assertion of differnt junit ldap methods
* broken test for directory server (and others)
* embedded context loading
* add user and query test
* UI: filter options passing filter and domain and onchange trigger
* disable tests that only work in ide
prereqs for domain-linkage fixed
move trigger to the right location in code
trigger for changing domain
* logging, comments and refactor
implement search users per domain
retrieve appropriate list of users to filter
get domain specific ldap provider
* query cloudstack users with now db filter
* recreate ldap linked account should succeed
* disable auto import users that don't exist
* ui choice and text
* import filter and potential remove from list bug fixed
* fix rights for domain admins
* list only member of linked groups not of principle group
* Do not show ldap user filter if not importing from ldap
do not delete un-needed items from dialog permanently
delete from temp object not from global one
* localdomain should not filterout users not imported from ldap
* several types of authentication handling errors fixed and unit tested
* conflict in output name
* add conflict source field to generic import dialog
* replace reflextion by enum member call
* conflict is now called conflict 🎉
This commit is contained in:
parent
9b7acfde1e
commit
5ff932eb86
@ -336,7 +336,10 @@ public class ApiConstants {
|
||||
public static final String URL = "url";
|
||||
public static final String USAGE_INTERFACE = "usageinterface";
|
||||
public static final String USER_DATA = "userdata";
|
||||
public static final String USER_FILTER = "userfilter";
|
||||
public static final String USER_ID = "userid";
|
||||
public static final String USER_SOURCE = "usersource";
|
||||
public static final String USER_CONFLICT_SOURCE = "conflictingusersource";
|
||||
public static final String USE_SSL = "ssl";
|
||||
public static final String USERNAME = "username";
|
||||
public static final String USER_CONFIGURABLE = "userconfigurable";
|
||||
|
||||
@ -111,6 +111,8 @@ public interface QueryService {
|
||||
|
||||
ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException;
|
||||
|
||||
ListResponse<UserResponse> searchForUsers(Long domainId, boolean recursive) throws PermissionDeniedException;
|
||||
|
||||
ListResponse<EventResponse> searchForEvents(ListEventsCmd cmd);
|
||||
|
||||
ListResponse<ResourceTagResponse> listTags(ListTagsCmd cmd);
|
||||
|
||||
@ -27,12 +27,23 @@
|
||||
<version>4.14.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<ads.version>2.0.0.AM25</ads.version>
|
||||
<gmaven.version>1.5</gmaven.version>
|
||||
<ldap-maven.version>1.1.3</ldap-maven.version>
|
||||
<ldapunit.version>1.1.3</ldapunit.version>
|
||||
<groovy.version>1.1-groovy-2.4</groovy.version>
|
||||
<zapdot.version>0.7</zapdot.version>
|
||||
<unboundedid.version>4.0.4</unboundedid.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmaven</groupId>
|
||||
<artifactId>gmaven-plugin</artifactId>
|
||||
<version>1.3</version>
|
||||
<version>${gmaven.version}</version>
|
||||
<configuration>
|
||||
<providerSelection>1.7</providerSelection>
|
||||
</configuration>
|
||||
@ -58,7 +69,7 @@
|
||||
<dependency>
|
||||
<groupId>org.codehaus.gmaven.runtime</groupId>
|
||||
<artifactId>gmaven-runtime-1.7</artifactId>
|
||||
<version>1.3</version>
|
||||
<version>${gmaven.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
@ -81,38 +92,126 @@
|
||||
<include>**/*Spec.groovy</include>
|
||||
<include>**/*Test.java</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.btmatthews.maven.plugins</groupId>
|
||||
<artifactId>ldap-maven-plugin</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<version>${ldap-maven.version}</version>
|
||||
<configuration>
|
||||
<monitorPort>11389</monitorPort>
|
||||
<monitorKey>ldap</monitorKey>
|
||||
<daemon>false</daemon>
|
||||
<rootDn>dc=cloudstack,dc=org</rootDn>
|
||||
<ldapPort>10389</ldapPort>
|
||||
<ldifFile>test/resources/cloudstack.org.ldif</ldifFile>
|
||||
<ldifFile>src/test/resources/cloudstack.org.ldif</ldifFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<testSourceDirectory>test</testSourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
</build>
|
||||
<dependencies>
|
||||
<!-- Mandatory dependencies for using Spock -->
|
||||
<dependency>
|
||||
<groupId>com.btmatthews.ldapunit</groupId>
|
||||
<artifactId>ldapunit</artifactId>
|
||||
<version>${ldapunit.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<version>1.1-groovy-2.4</version>
|
||||
<version>${groovy.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Optional dependencies for using Spock -->
|
||||
<dependency> <!-- enables mocking of classes (in addition to interfaces) -->
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib-nodep</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zapodot</groupId>
|
||||
<artifactId>embedded-ldap-junit</artifactId>
|
||||
<version>${zapdot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.unboundid</groupId>
|
||||
<artifactId>unboundid-ldapsdk</artifactId>
|
||||
<version>${unboundedid.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>${cs.mockito.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${cs.junit.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-server-integ</artifactId>
|
||||
<version>${ads.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!--
|
||||
shared-ldap-schema module needs to be excluded to avoid multiple schema resources on the classpath
|
||||
-->
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.shared</groupId>
|
||||
<artifactId>shared-ldap-schema</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core-constants</artifactId>
|
||||
<version>${ads.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core-annotations</artifactId>
|
||||
<version>${ads.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core</artifactId>
|
||||
<version>${ads.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-protocol-ldap</artifactId>
|
||||
<version>${ads.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-jdbm-partition</artifactId>
|
||||
<version>${ads.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-ldif-partition</artifactId>
|
||||
<version>${ads.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${cs.commons-io.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.cloudstack.api;
|
||||
|
||||
public interface LdapConstants {
|
||||
String PRINCIPAL = "principal";
|
||||
}
|
||||
@ -16,18 +16,26 @@
|
||||
// under the License.
|
||||
package org.apache.cloudstack.api.command;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.BaseListCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.admin.user.ListUsersCmd;
|
||||
import org.apache.cloudstack.api.response.LdapUserResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.UserResponse;
|
||||
@ -38,8 +46,37 @@ import org.apache.cloudstack.query.QueryService;
|
||||
|
||||
import com.cloud.user.Account;
|
||||
|
||||
@APICommand(name = "listLdapUsers", responseObject = LdapUserResponse.class, description = "Lists all LDAP Users", since = "4.2.0",
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
|
||||
/**
|
||||
* a short flow, use plantuml to view (see http://plantuml.com)
|
||||
* @startuml
|
||||
* start
|
||||
* :list ldap users request;
|
||||
* :get ldap binding;
|
||||
* if (domain == null) then (true)
|
||||
* :get global trust domain;
|
||||
* else (false)
|
||||
* :get trustdomain for domain;
|
||||
* endif
|
||||
* :get ldap users\n using trust domain;
|
||||
* if (filter == 'NoFilter') then (pass as is)
|
||||
* elseif (filter == 'AnyDomain') then (anydomain)
|
||||
* :filterList = all\n\t\tcloudstack\n\t\tusers;
|
||||
* elseif (filter == 'LocalDomain')
|
||||
* :filterList = local users\n\t\tfor domain;
|
||||
* elseif (filter == 'PotentialImport') then (address account\nsynchronisation\nconfigurations)
|
||||
* :query\n the account\n bindings;
|
||||
* :check and markup\n ldap users\n for bound OUs\n with usersource;
|
||||
* else ( unknown value for filter )
|
||||
* :throw invalid parameter;
|
||||
* stop
|
||||
* endif
|
||||
* :remove users in filterList\nfrom ldap users list;
|
||||
* :return remaining;
|
||||
* stop
|
||||
* @enduml
|
||||
*/
|
||||
@APICommand(name = "listLdapUsers", responseObject = LdapUserResponse.class, description = "Lists LDAP Users according to the specifications from the user request.", since = "4.2.0",
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin,RoleType.DomainAdmin})
|
||||
public class LdapListUsersCmd extends BaseListCmd {
|
||||
|
||||
public static final Logger s_logger = Logger.getLogger(LdapListUsersCmd.class.getName());
|
||||
@ -47,15 +84,29 @@ public class LdapListUsersCmd extends BaseListCmd {
|
||||
@Inject
|
||||
private LdapManager _ldapManager;
|
||||
|
||||
@Inject
|
||||
private QueryService _queryService;
|
||||
|
||||
@Parameter(name = "listtype",
|
||||
type = CommandType.STRING,
|
||||
required = false,
|
||||
description = "Determines whether all ldap users are returned or just non-cloudstack users")
|
||||
type = CommandType.STRING,
|
||||
required = false,
|
||||
description = "Determines whether all ldap users are returned or just non-cloudstack users. This option is deprecated in favour for the more option rich 'userfilter' parameter")
|
||||
@Deprecated
|
||||
private String listType;
|
||||
|
||||
@Parameter(name = ApiConstants.USER_FILTER,
|
||||
type = CommandType.STRING,
|
||||
required = false,
|
||||
since = "4.13",
|
||||
description = "Determines what type of filter is applied on the list of users returned from LDAP.\n"
|
||||
+ "\tvalid values are\n"
|
||||
+ "\t'NoFilter'\t no filtering is done,\n"
|
||||
+ "\t'LocalDomain'\tusers already in the current or requested domain will be filtered out of the result list,\n"
|
||||
+ "\t'AnyDomain'\tusers that already exist anywhere in cloudstack will be filtered out, and\n"
|
||||
+ "\t'PotentialImport'\tall users that would be automatically imported from the listing will be shown,"
|
||||
+ " including those that are already in cloudstack, the later will be annotated with their userSource")
|
||||
private String userFilter;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "linked domain")
|
||||
private Long domainId;
|
||||
|
||||
public LdapListUsersCmd() {
|
||||
super();
|
||||
}
|
||||
@ -66,27 +117,35 @@ public class LdapListUsersCmd extends BaseListCmd {
|
||||
_queryService = queryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* (as a check for isACloudstackUser is done) only non cloudstack users should be shown
|
||||
* @param users a list of {@code LdapUser}s
|
||||
* @return a (filtered?) list of user response objects
|
||||
*/
|
||||
private List<LdapUserResponse> createLdapUserResponse(final List<LdapUser> users) {
|
||||
final List<LdapUserResponse> ldapResponses = new ArrayList<LdapUserResponse>();
|
||||
for (final LdapUser user : users) {
|
||||
if (getListType().equals("all") || !isACloudstackUser(user)) {
|
||||
final LdapUserResponse ldapResponse = _ldapManager.createLdapUserResponse(user);
|
||||
ldapResponse.setObjectName("LdapUser");
|
||||
ldapResponses.add(ldapResponse);
|
||||
}
|
||||
final LdapUserResponse ldapResponse = _ldapManager.createLdapUserResponse(user);
|
||||
ldapResponse.setObjectName("LdapUser");
|
||||
ldapResponses.add(ldapResponse);
|
||||
}
|
||||
return ldapResponses;
|
||||
}
|
||||
|
||||
private List<UserResponse> cloudstackUsers = null;
|
||||
|
||||
@Override
|
||||
public void execute() throws ServerApiException {
|
||||
List<LdapUserResponse> ldapResponses = null;
|
||||
cloudstackUsers = null;
|
||||
List<LdapUserResponse> ldapResponses = new ArrayList<LdapUserResponse>();
|
||||
final ListResponse<LdapUserResponse> response = new ListResponse<LdapUserResponse>();
|
||||
try {
|
||||
final List<LdapUser> users = _ldapManager.getUsers(null);
|
||||
final List<LdapUser> users = _ldapManager.getUsers(domainId);
|
||||
ldapResponses = createLdapUserResponse(users);
|
||||
// now filter and annotate
|
||||
ldapResponses = applyUserFilter(ldapResponses);
|
||||
} catch (final NoLdapUserMatchingQueryException ex) {
|
||||
ldapResponses = new ArrayList<LdapUserResponse>();
|
||||
// ok, we'll make do with the empty list ldapResponses = new ArrayList<LdapUserResponse>();
|
||||
} finally {
|
||||
response.setResponses(ldapResponses);
|
||||
response.setResponseName(getCommandName());
|
||||
@ -94,6 +153,43 @@ public class LdapListUsersCmd extends BaseListCmd {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get a list of relevant cloudstack users, depending on the userFilter
|
||||
*/
|
||||
private List<UserResponse> getCloudstackUsers() {
|
||||
if (cloudstackUsers == null) {
|
||||
try {
|
||||
cloudstackUsers = getUserFilter().getCloudstackUserList(this).getResponses();
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new CloudRuntimeException("error in program login; we are not filtering but still querying users to filter???", e);
|
||||
}
|
||||
traceUserList();
|
||||
}
|
||||
return cloudstackUsers;
|
||||
}
|
||||
|
||||
private void traceUserList() {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
StringBuilder users = new StringBuilder();
|
||||
for (UserResponse user : cloudstackUsers) {
|
||||
if (users.length()> 0) {
|
||||
users.append(", ");
|
||||
}
|
||||
users.append(user.getUsername());
|
||||
}
|
||||
|
||||
s_logger.trace(String.format("checking against %d cloudstackusers: %s.", this.cloudstackUsers.size(), users.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private List<LdapUserResponse> applyUserFilter(List<LdapUserResponse> ldapResponses) {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace(String.format("applying filter: %s or %s.", this.getListTypeString(), this.getUserFilter()));
|
||||
}
|
||||
List<LdapUserResponse> responseList = getUserFilter().filter(this,ldapResponses);
|
||||
return responseList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return s_name;
|
||||
@ -104,20 +200,306 @@ public class LdapListUsersCmd extends BaseListCmd {
|
||||
return Account.ACCOUNT_ID_SYSTEM;
|
||||
}
|
||||
|
||||
private String getListType() {
|
||||
String getListTypeString() {
|
||||
return listType == null ? "all" : listType;
|
||||
}
|
||||
|
||||
private boolean isACloudstackUser(final LdapUser ldapUser) {
|
||||
final ListResponse<UserResponse> response = _queryService.searchForUsers(new ListUsersCmd());
|
||||
final List<UserResponse> cloudstackUsers = response.getResponses();
|
||||
if (cloudstackUsers != null && cloudstackUsers.size() != 0) {
|
||||
for (final UserResponse cloudstackUser : response.getResponses()) {
|
||||
String getUserFilterString() {
|
||||
return userFilter == null ? getListTypeString() == null ? "NoFilter" : getListTypeString().equals("all") ? "NoFilter" : "AnyDomain" : userFilter;
|
||||
}
|
||||
|
||||
UserFilter getUserFilter() {
|
||||
return UserFilter.fromString(getUserFilterString());
|
||||
}
|
||||
|
||||
boolean isACloudstackUser(final LdapUser ldapUser) {
|
||||
boolean rc = false;
|
||||
final List<UserResponse> cloudstackUsers = getCloudstackUsers();
|
||||
if (cloudstackUsers != null) {
|
||||
for (final UserResponse cloudstackUser : cloudstackUsers) {
|
||||
if (ldapUser.getUsername().equals(cloudstackUser.getUsername())) {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace(String.format("found user %s in cloudstack", ldapUser.getUsername()));
|
||||
}
|
||||
|
||||
rc = true;
|
||||
} else {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace(String.format("ldap user %s does not match cloudstack user", ldapUser.getUsername(), cloudstackUser.getUsername()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
boolean isACloudstackUser(final LdapUserResponse ldapUser) {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("checking response : " + ldapUser.toString());
|
||||
}
|
||||
final List<UserResponse> cloudstackUsers = getCloudstackUsers();
|
||||
if (cloudstackUsers != null && cloudstackUsers.size() != 0) {
|
||||
for (final UserResponse cloudstackUser : cloudstackUsers) {
|
||||
if (ldapUser.getUsername().equals(cloudstackUser.getUsername())) {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace(String.format("found user %s in cloudstack", ldapUser.getUsername()));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace(String.format("ldap user %s does not match cloudstack user", ldapUser.getUsername(), cloudstackUser.getUsername()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* typecheck for userfilter values and filter type dependend functionalities.
|
||||
* This could have been in two switch statements elsewhere in the code.
|
||||
* Arguably this is a cleaner solution.
|
||||
*/
|
||||
enum UserFilter {
|
||||
NO_FILTER("NoFilter"){
|
||||
@Override public List<LdapUserResponse> filter(LdapListUsersCmd cmd, List<LdapUserResponse> input) {
|
||||
return cmd.filterNoFilter(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* in case of no filter we should find all users in the current domain for annotation.
|
||||
*/
|
||||
@Override public ListResponse<UserResponse> getCloudstackUserList(LdapListUsersCmd cmd) {
|
||||
return cmd._queryService.searchForUsers(cmd.domainId,true);
|
||||
|
||||
}
|
||||
},
|
||||
LOCAL_DOMAIN("LocalDomain"){
|
||||
@Override public List<LdapUserResponse> filter(LdapListUsersCmd cmd, List<LdapUserResponse> input) {
|
||||
return cmd.filterLocalDomain(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* if we are filtering for local domain, only get users for the current domain
|
||||
*/
|
||||
@Override public ListResponse<UserResponse> getCloudstackUserList(LdapListUsersCmd cmd) {
|
||||
return cmd._queryService.searchForUsers(cmd.domainId,false);
|
||||
}
|
||||
},
|
||||
ANY_DOMAIN("AnyDomain"){
|
||||
@Override public List<LdapUserResponse> filter(LdapListUsersCmd cmd, List<LdapUserResponse> input) {
|
||||
return cmd.filterAnyDomain(input);
|
||||
}
|
||||
|
||||
/*
|
||||
* if we are filtering for any domain, get recursive all users for the root domain
|
||||
*/
|
||||
@Override public ListResponse<UserResponse> getCloudstackUserList(LdapListUsersCmd cmd) {
|
||||
return cmd._queryService.searchForUsers(CallContext.current().getCallingAccount().getDomainId(), true);
|
||||
}
|
||||
},
|
||||
POTENTIAL_IMPORT("PotentialImport"){
|
||||
@Override public List<LdapUserResponse> filter(LdapListUsersCmd cmd, List<LdapUserResponse> input) {
|
||||
return cmd.filterPotentialImport(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* if we are filtering for potential imports,
|
||||
* we are only looking for users in the linked domains/accounts,
|
||||
* which is only relevant if we ask ldap users for this domain.
|
||||
* So we are asking for all users in the current domain as well
|
||||
*/
|
||||
@Override public ListResponse<UserResponse> getCloudstackUserList(LdapListUsersCmd cmd) {
|
||||
return cmd._queryService.searchForUsers(cmd.domainId,false);
|
||||
}
|
||||
};
|
||||
|
||||
private final String value;
|
||||
|
||||
UserFilter(String val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
public abstract List<LdapUserResponse> filter(LdapListUsersCmd cmd, List<LdapUserResponse> input);
|
||||
|
||||
public abstract ListResponse<UserResponse> getCloudstackUserList(LdapListUsersCmd cmd);
|
||||
|
||||
static UserFilter fromString(String val) {
|
||||
if(NO_FILTER.toString().equalsIgnoreCase(val)) {
|
||||
return NO_FILTER;
|
||||
} else if (LOCAL_DOMAIN.toString().equalsIgnoreCase(val)) {
|
||||
return LOCAL_DOMAIN;
|
||||
} else if(ANY_DOMAIN.toString().equalsIgnoreCase(val)) {
|
||||
return ANY_DOMAIN;
|
||||
} else if(POTENTIAL_IMPORT.toString().equalsIgnoreCase(val)) {
|
||||
return POTENTIAL_IMPORT;
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("%s is not a legal 'UserFilter' value", val));
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* no filtering but improve with annotation of source for existing ACS users
|
||||
* @param input ldap response list of users
|
||||
* @return unfiltered list of the input list of ldap users
|
||||
*/
|
||||
public List<LdapUserResponse> filterNoFilter(List<LdapUserResponse> input) {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("returning unfiltered list of ldap users");
|
||||
}
|
||||
annotateUserListWithSources(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* filter the list of ldap users. no users visible to the caller should be in the returned list
|
||||
* @param input ldap response list of users
|
||||
* @return a list of ldap users not already in ACS
|
||||
*/
|
||||
public List<LdapUserResponse> filterAnyDomain(List<LdapUserResponse> input) {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("filtering existing users");
|
||||
}
|
||||
final List<LdapUserResponse> ldapResponses = new ArrayList<LdapUserResponse>();
|
||||
for (final LdapUserResponse user : input) {
|
||||
if (isNotAlreadyImportedInTheCurrentDomain(user)) {
|
||||
ldapResponses.add(user);
|
||||
}
|
||||
}
|
||||
annotateUserListWithSources(ldapResponses);
|
||||
|
||||
return ldapResponses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true unless the the user is imported in the specified cloudstack domain from LDAP
|
||||
*/
|
||||
private boolean isNotAlreadyImportedInTheCurrentDomain(LdapUserResponse user) {
|
||||
UserResponse cloudstackUser = getCloudstackUser(user);
|
||||
String domainId = getCurrentDomainId();
|
||||
|
||||
return cloudstackUser == null /*doesn't exist in cloudstack*/
|
||||
|| ! (
|
||||
cloudstackUser.getUserSource().equalsIgnoreCase(User.Source.LDAP.toString())
|
||||
&& domainId.equals(cloudstackUser.getDomainId())); /* is from another source */
|
||||
}
|
||||
|
||||
/**
|
||||
* filter the list of ldap users. no users visible to the caller already in the domain specified should be in the returned list
|
||||
* @param input ldap response list of users
|
||||
* @return a list of ldap users not already in ACS
|
||||
*/
|
||||
public List<LdapUserResponse> filterLocalDomain(List<LdapUserResponse> input) {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("filtering local domain users");
|
||||
}
|
||||
final List<LdapUserResponse> ldapResponses = new ArrayList<LdapUserResponse>();
|
||||
String domainId = getCurrentDomainId();
|
||||
for (final LdapUserResponse user : input) {
|
||||
UserResponse cloudstackUser = getCloudstackUser(user);
|
||||
if (cloudstackUser == null /*doesn't exist in cloudstack*/
|
||||
|| !domainId.equals(cloudstackUser.getDomainId()) /* doesn't exist in this domain */
|
||||
|| !cloudstackUser.getUserSource().equalsIgnoreCase(User.Source.LDAP.toString()) /* is from another source */
|
||||
) {
|
||||
ldapResponses.add(user);
|
||||
}
|
||||
}
|
||||
annotateUserListWithSources(ldapResponses);
|
||||
return ldapResponses;
|
||||
}
|
||||
|
||||
private String getCurrentDomainId() {
|
||||
String domainId = null;
|
||||
if (this.domainId != null) {
|
||||
Domain domain = _domainService.getDomain(this.domainId);
|
||||
domainId = domain.getUuid();
|
||||
} else {
|
||||
final CallContext callContext = CallContext.current();
|
||||
domainId = _domainService.getDomain(callContext.getCallingAccount().getDomainId()).getUuid();
|
||||
}
|
||||
return domainId;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param input a list of ldap users
|
||||
* @return annotated list of the users of the input list, that will be automatically imported or synchronised
|
||||
*/
|
||||
public List<LdapUserResponse> filterPotentialImport(List<LdapUserResponse> input) {
|
||||
if(s_logger.isTraceEnabled()) {
|
||||
s_logger.trace("should be filtering potential imports!!!");
|
||||
}
|
||||
// functional possibility do not add only users not yet in cloudstack but include users that would be moved if they are so in ldap?
|
||||
// this means if they are part of a account linked to an ldap group/ou
|
||||
input.removeIf(ldapUser ->
|
||||
(
|
||||
(isACloudstackUser(ldapUser))
|
||||
&& (getCloudstackUser(ldapUser).getUserSource().equalsIgnoreCase(User.Source.LDAP.toString()))
|
||||
)
|
||||
);
|
||||
annotateUserListWithSources(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
private void annotateUserListWithSources(List<LdapUserResponse> input) {
|
||||
for (final LdapUserResponse user : input) {
|
||||
annotateCloudstackSource(user);
|
||||
}
|
||||
}
|
||||
|
||||
private void annotateCloudstackSource(LdapUserResponse user) {
|
||||
final UserResponse cloudstackUser = getCloudstackUser(user);
|
||||
if (cloudstackUser != null) {
|
||||
user.setUserSource(cloudstackUser.getUserSource());
|
||||
} else {
|
||||
user.setUserSource("");
|
||||
}
|
||||
}
|
||||
|
||||
private UserResponse getCloudstackUser(LdapUserResponse user) {
|
||||
UserResponse returnObject = null;
|
||||
final List<UserResponse> cloudstackUsers = getCloudstackUsers();
|
||||
if (cloudstackUsers != null) {
|
||||
for (final UserResponse cloudstackUser : cloudstackUsers) {
|
||||
if (user.getUsername().equals(cloudstackUser.getUsername())) {
|
||||
returnObject = cloudstackUser;
|
||||
if (returnObject.getDomainId() == this.getCurrentDomainId()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnObject;
|
||||
}
|
||||
|
||||
private void checkFilterMethodType(Type returnType) {
|
||||
String msg = null;
|
||||
if (returnType instanceof ParameterizedType) {
|
||||
ParameterizedType type = (ParameterizedType) returnType;
|
||||
if(type.getRawType().equals(List.class)) {
|
||||
Type[] typeArguments = type.getActualTypeArguments();
|
||||
if (typeArguments.length == 1) {
|
||||
if (typeArguments[0].equals(LdapUserResponse.class)) {
|
||||
// we're good'
|
||||
} else {
|
||||
msg = new String("list of return type contains " + typeArguments[0].getTypeName());
|
||||
}
|
||||
} else {
|
||||
msg = String.format("type %s has to the wrong number of arguments", type.getRawType());
|
||||
}
|
||||
} else {
|
||||
msg = String.format("type %s is not a List<>", type.getTypeName());
|
||||
}
|
||||
} else {
|
||||
msg = new String("can't even begin to explain; review your method signature");
|
||||
}
|
||||
if(msg != null) {
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -18,35 +18,41 @@ package org.apache.cloudstack.api.response;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import org.apache.cloudstack.api.LdapConstants;
|
||||
|
||||
public class LdapUserResponse extends BaseResponse {
|
||||
@SerializedName("email")
|
||||
@SerializedName(ApiConstants.EMAIL)
|
||||
@Param(description = "The user's email")
|
||||
private String email;
|
||||
|
||||
@SerializedName("principal")
|
||||
@SerializedName(LdapConstants.PRINCIPAL)
|
||||
@Param(description = "The user's principle")
|
||||
private String principal;
|
||||
|
||||
@SerializedName("firstname")
|
||||
@SerializedName(ApiConstants.FIRSTNAME)
|
||||
@Param(description = "The user's firstname")
|
||||
private String firstname;
|
||||
|
||||
@SerializedName("lastname")
|
||||
@SerializedName(ApiConstants.LASTNAME)
|
||||
@Param(description = "The user's lastname")
|
||||
private String lastname;
|
||||
|
||||
@SerializedName("username")
|
||||
@SerializedName(ApiConstants.USERNAME)
|
||||
@Param(description = "The user's username")
|
||||
private String username;
|
||||
|
||||
@SerializedName("domain")
|
||||
@SerializedName(ApiConstants.DOMAIN)
|
||||
@Param(description = "The user's domain")
|
||||
private String domain;
|
||||
|
||||
@SerializedName(ApiConstants.USER_CONFLICT_SOURCE)
|
||||
@Param(description = "The authentication source for this user as known to the system or empty if the user is not yet in cloudstack.")
|
||||
private String userSource;
|
||||
|
||||
public LdapUserResponse() {
|
||||
super();
|
||||
}
|
||||
@ -61,6 +67,11 @@ public class LdapUserResponse extends BaseResponse {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public LdapUserResponse(final String username, final String email, final String firstname, final String lastname, final String principal, String domain, String userSource) {
|
||||
this(username, email, firstname, lastname, principal, domain);
|
||||
setUserSource(userSource);
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
@ -85,6 +96,10 @@ public class LdapUserResponse extends BaseResponse {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public String getUserSource() {
|
||||
return userSource;
|
||||
}
|
||||
|
||||
public void setEmail(final String email) {
|
||||
this.email = email;
|
||||
}
|
||||
@ -108,4 +123,67 @@ public class LdapUserResponse extends BaseResponse {
|
||||
public void setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public void setUserSource(String userSource) {
|
||||
this.userSource = userSource;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
final String COLUMN = ": ";
|
||||
final String COMMA = ", ";
|
||||
StringBuilder selfRepresentation = new StringBuilder();
|
||||
selfRepresentation.append(this.getClass().getName());
|
||||
selfRepresentation.append('{');
|
||||
boolean hascontent = false;
|
||||
if (this.getUsername() != null) {
|
||||
selfRepresentation.append(ApiConstants.USERNAME);
|
||||
selfRepresentation.append(COLUMN);
|
||||
selfRepresentation.append(this.getUsername());
|
||||
hascontent = true;
|
||||
}
|
||||
if (this.getFirstname() != null) {
|
||||
if(hascontent) selfRepresentation.append(COMMA);
|
||||
selfRepresentation.append(ApiConstants.FIRSTNAME);
|
||||
selfRepresentation.append(COLUMN);
|
||||
selfRepresentation.append(this.getFirstname());
|
||||
hascontent = true;
|
||||
}
|
||||
if (this.getLastname() != null) {
|
||||
if(hascontent) selfRepresentation.append(COMMA);
|
||||
selfRepresentation.append(ApiConstants.LASTNAME);
|
||||
selfRepresentation.append(COLUMN);
|
||||
selfRepresentation.append(this.getLastname());
|
||||
hascontent = true;
|
||||
}
|
||||
if(this.getDomain() != null) {
|
||||
if(hascontent) selfRepresentation.append(COMMA);
|
||||
selfRepresentation.append(ApiConstants.DOMAIN);
|
||||
selfRepresentation.append(COLUMN);
|
||||
selfRepresentation.append(this.getDomain());
|
||||
hascontent = true;
|
||||
}
|
||||
if (this.getEmail() != null) {
|
||||
if(hascontent) selfRepresentation.append(COMMA);
|
||||
selfRepresentation.append(ApiConstants.EMAIL);
|
||||
selfRepresentation.append(COLUMN);
|
||||
selfRepresentation.append(this.getEmail());
|
||||
hascontent = true;
|
||||
}
|
||||
if (this.getPrincipal() != null) {
|
||||
if(hascontent) selfRepresentation.append(COMMA);
|
||||
selfRepresentation.append(LdapConstants.PRINCIPAL);
|
||||
selfRepresentation.append(COLUMN);
|
||||
selfRepresentation.append(this.getPrincipal());
|
||||
hascontent = true;
|
||||
}
|
||||
if (this.getUserSource() != null) {
|
||||
if (hascontent) selfRepresentation.append(COMMA);
|
||||
selfRepresentation.append(ApiConstants.USER_CONFLICT_SOURCE);
|
||||
selfRepresentation.append(COLUMN);
|
||||
selfRepresentation.append(this.getUserSource());
|
||||
}
|
||||
selfRepresentation.append('}');
|
||||
|
||||
return selfRepresentation.toString();
|
||||
}
|
||||
}
|
||||
@ -38,7 +38,7 @@ import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class LdapAuthenticator extends AdapterBase implements UserAuthenticator {
|
||||
private static final Logger s_logger = Logger.getLogger(LdapAuthenticator.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(LdapAuthenticator.class.getName());
|
||||
|
||||
@Inject
|
||||
private LdapManager _ldapManager;
|
||||
@ -61,32 +61,51 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
|
||||
public Pair<Boolean, ActionOnFailedAuthentication> authenticate(final String username, final String password, final Long domainId, final Map<String, Object[]> requestParameters) {
|
||||
Pair<Boolean, ActionOnFailedAuthentication> rc = new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
|
||||
|
||||
// TODO not allowing an empty password is a policy we shouldn't decide on. A private cloud may well want to allow this.
|
||||
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
||||
s_logger.debug("Username or Password cannot be empty");
|
||||
return rc;
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Retrieving ldap user: " + username);
|
||||
}
|
||||
|
||||
if (_ldapManager.isLdapEnabled()) {
|
||||
final UserAccount user = _userAccountDao.getUserAccount(username, domainId);
|
||||
List<LdapTrustMapVO> ldapTrustMapVOs = _ldapManager.getDomainLinkage(domainId);
|
||||
if(ldapTrustMapVOs != null && ldapTrustMapVOs.size() > 0) {
|
||||
if(ldapTrustMapVOs.size() == 1 && ldapTrustMapVOs.get(0).getAccountId() == 0) {
|
||||
// We have a single mapping of a domain to an ldap group or ou
|
||||
return authenticate(username, password, domainId, user, ldapTrustMapVOs.get(0));
|
||||
} else {
|
||||
// we are dealing with mapping of accounts in a domain to ldap groups
|
||||
return authenticate(username, password, domainId, user, ldapTrustMapVOs);
|
||||
// TODO not allowing an empty password is a policy we shouldn't decide on. A private cloud may well want to allow this.
|
||||
if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
|
||||
if (_ldapManager.isLdapEnabled(domainId) || _ldapManager.isLdapEnabled()) {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("LDAP is enabled in the ldapManager");
|
||||
}
|
||||
final UserAccount user = _userAccountDao.getUserAccount(username, domainId);
|
||||
if (user != null && ! User.Source.LDAP.equals(user.getSource())) {
|
||||
return rc;
|
||||
}
|
||||
List<LdapTrustMapVO> ldapTrustMapVOs = getLdapTrustMapVOS(domainId);
|
||||
if(ldapTrustMapVOs != null && ldapTrustMapVOs.size() > 0) {
|
||||
if(ldapTrustMapVOs.size() == 1 && ldapTrustMapVOs.get(0).getAccountId() == 0) {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("We have a single mapping of a domain to an ldap group or ou");
|
||||
}
|
||||
rc = authenticate(username, password, domainId, user, ldapTrustMapVOs.get(0));
|
||||
} else {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("we are dealing with mapping of accounts in a domain to ldap groups");
|
||||
}
|
||||
rc = authenticate(username, password, domainId, user, ldapTrustMapVOs);
|
||||
}
|
||||
} else {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace(String.format("'this' domain (%d) is not linked to ldap follow normal authentication", domainId));
|
||||
}
|
||||
rc = authenticate(username, password, domainId, user);
|
||||
}
|
||||
} else {
|
||||
//domain is not linked to ldap follow normal authentication
|
||||
return authenticate(username, password, domainId, user);
|
||||
}
|
||||
} else {
|
||||
LOGGER.debug("Username or Password cannot be empty");
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
private List<LdapTrustMapVO> getLdapTrustMapVOS(Long domainId) {
|
||||
return _ldapManager.getDomainLinkage(domainId);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the user exists in ldap and create in cloudstack if needed.
|
||||
*
|
||||
@ -97,13 +116,16 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
|
||||
* @param ldapTrustMapVOs the trust mappings of accounts in the domain to ldap groups
|
||||
* @return false if the ldap user object does not exist, is not mapped to an account, is mapped to multiple accounts or if authenitication fails
|
||||
*/
|
||||
private Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, UserAccount userAccount, List<LdapTrustMapVO> ldapTrustMapVOs) {
|
||||
Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, UserAccount userAccount, List<LdapTrustMapVO> ldapTrustMapVOs) {
|
||||
Pair<Boolean, ActionOnFailedAuthentication> rc = new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
|
||||
try {
|
||||
LdapUser ldapUser = _ldapManager.getUser(username, domainId);
|
||||
List<String> memberships = ldapUser.getMemberships();
|
||||
tracelist("memberships for " + username, memberships);
|
||||
List<String> mappedGroups = getMappedGroups(ldapTrustMapVOs);
|
||||
tracelist("mappedgroups for " + username, mappedGroups);
|
||||
mappedGroups.retainAll(memberships);
|
||||
tracelist("actual groups for " + username, mappedGroups);
|
||||
// check membership, there must be only one match in this domain
|
||||
if(ldapUser.isDisabled()) {
|
||||
logAndDisable(userAccount, "attempt to log on using disabled ldap user " + userAccount.getUsername(), false);
|
||||
@ -115,9 +137,16 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
|
||||
// a valid ldap configured user exists
|
||||
LdapTrustMapVO mapping = _ldapManager.getLinkedLdapGroup(domainId,mappedGroups.get(0));
|
||||
// we could now assert that ldapTrustMapVOs.contains(mapping);
|
||||
// createUser in Account can only be done by account name not by account id
|
||||
String accountName = _accountManager.getAccount(mapping.getAccountId()).getAccountName();
|
||||
// createUser in Account can only be done by account name not by account id;
|
||||
Account account = _accountManager.getAccount(mapping.getAccountId());
|
||||
if(null == account) {
|
||||
throw new CloudRuntimeException(String.format("account for user (%s) not found by id %d", username, mapping.getAccountId()));
|
||||
}
|
||||
String accountName = account.getAccountName();
|
||||
rc.first(_ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId));
|
||||
if (! rc.first()) {
|
||||
rc.second(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
|
||||
}
|
||||
// for security reasons we keep processing on faulty login attempt to not give a way information on userid existence
|
||||
if (userAccount == null) {
|
||||
// new user that is in ldap; authenticate and create
|
||||
@ -146,16 +175,29 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
|
||||
}
|
||||
}
|
||||
} catch (NoLdapUserMatchingQueryException e) {
|
||||
s_logger.debug(e.getMessage());
|
||||
LOGGER.debug(e.getMessage());
|
||||
disableUserInCloudStack(userAccount);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
private void tracelist(String msg, List<String> listToTrace) {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
StringBuilder logMsg = new StringBuilder();
|
||||
logMsg.append(msg);
|
||||
logMsg.append(':');
|
||||
for (String listMember : listToTrace) {
|
||||
logMsg.append(' ');
|
||||
logMsg.append(listMember);
|
||||
}
|
||||
LOGGER.trace(logMsg.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void logAndDisable(UserAccount userAccount, String msg, boolean remove) {
|
||||
if (s_logger.isInfoEnabled()) {
|
||||
s_logger.info(msg);
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info(msg);
|
||||
}
|
||||
if(remove) {
|
||||
removeUserInCloudStack(userAccount);
|
||||
@ -164,7 +206,7 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getMappedGroups(List<LdapTrustMapVO> ldapTrustMapVOs) {
|
||||
List<String> getMappedGroups(List<LdapTrustMapVO> ldapTrustMapVOs) {
|
||||
List<String> groups = new ArrayList<>();
|
||||
for (LdapTrustMapVO vo : ldapTrustMapVOs) {
|
||||
groups.add(vo.getName());
|
||||
@ -188,7 +230,9 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
|
||||
final short accountType = ldapTrustMapVO.getAccountType();
|
||||
processLdapUser(password, domainId, user, rc, ldapUser, accountType);
|
||||
} catch (NoLdapUserMatchingQueryException e) {
|
||||
s_logger.debug(e.getMessage());
|
||||
LOGGER.debug(e.getMessage());
|
||||
// no user in ldap ==>> disable user in cloudstack
|
||||
disableUserInCloudStack(user);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -229,12 +273,16 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
|
||||
if(!ldapUser.isDisabled()) {
|
||||
result = _ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId);
|
||||
} else {
|
||||
s_logger.debug("user with principal "+ ldapUser.getPrincipal() + " is disabled in ldap");
|
||||
LOGGER.debug("user with principal "+ ldapUser.getPrincipal() + " is disabled in ldap");
|
||||
}
|
||||
} catch (NoLdapUserMatchingQueryException e) {
|
||||
s_logger.debug(e.getMessage());
|
||||
LOGGER.debug(e.getMessage());
|
||||
}
|
||||
}
|
||||
return processResultAndAction(user, result);
|
||||
}
|
||||
|
||||
private Pair<Boolean, ActionOnFailedAuthentication> processResultAndAction(UserAccount user, boolean result) {
|
||||
return (!result && user != null) ?
|
||||
new Pair<Boolean, ActionOnFailedAuthentication>(result, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT):
|
||||
new Pair<Boolean, ActionOnFailedAuthentication>(result, null);
|
||||
|
||||
@ -38,7 +38,6 @@ public interface LdapManager extends PluggableService {
|
||||
|
||||
LdapConfigurationResponse addConfiguration(final LdapAddConfigurationCmd cmd) throws InvalidParameterValueException;
|
||||
|
||||
@Deprecated
|
||||
LdapConfigurationResponse addConfiguration(String hostname, int port, Long domainId) throws InvalidParameterValueException;
|
||||
|
||||
boolean canAuthenticate(String principal, String password, final Long domainId);
|
||||
@ -62,6 +61,8 @@ public interface LdapManager extends PluggableService {
|
||||
|
||||
boolean isLdapEnabled();
|
||||
|
||||
boolean isLdapEnabled(long domainId);
|
||||
|
||||
Pair<List<? extends LdapConfigurationVO>, Integer> listConfigurations(LdapListConfigurationCmd cmd);
|
||||
|
||||
List<LdapUser> searchUsers(String query) throws NoLdapUserMatchingQueryException;
|
||||
|
||||
@ -25,6 +25,7 @@ import javax.naming.NamingException;
|
||||
import javax.naming.ldap.LdapContext;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.api.LdapValidator;
|
||||
import org.apache.cloudstack.api.command.LDAPConfigCmd;
|
||||
import org.apache.cloudstack.api.command.LDAPRemoveCmd;
|
||||
@ -57,7 +58,7 @@ import com.cloud.utils.Pair;
|
||||
|
||||
@Component
|
||||
public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
private static final Logger s_logger = Logger.getLogger(LdapManagerImpl.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(LdapManagerImpl.class.getName());
|
||||
|
||||
@Inject
|
||||
private LdapConfigurationDao _ldapConfigurationDao;
|
||||
@ -79,14 +80,13 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
@Inject
|
||||
LdapTrustMapDao _ldapTrustMapDao;
|
||||
|
||||
|
||||
public LdapManagerImpl() {
|
||||
super();
|
||||
}
|
||||
|
||||
public LdapManagerImpl(final LdapConfigurationDao ldapConfigurationDao, final LdapContextFactory ldapContextFactory, final LdapUserManagerFactory ldapUserManagerFactory,
|
||||
final LdapConfiguration ldapConfiguration) {
|
||||
super();
|
||||
this();
|
||||
_ldapConfigurationDao = ldapConfigurationDao;
|
||||
_ldapContextFactory = ldapContextFactory;
|
||||
_ldapUserManagerFactory = ldapUserManagerFactory;
|
||||
@ -118,10 +118,10 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
context = _ldapContextFactory.createBindContext(providerUrl,domainId);
|
||||
configuration = new LdapConfigurationVO(hostname, port, domainId);
|
||||
_ldapConfigurationDao.persist(configuration);
|
||||
s_logger.info("Added new ldap server with url: " + providerUrl + (domainId == null ? "": " for domain " + domainId));
|
||||
LOGGER.info("Added new ldap server with url: " + providerUrl + (domainId == null ? "": " for domain " + domainId));
|
||||
return createLdapConfigurationResponse(configuration);
|
||||
} catch (NamingException | IOException e) {
|
||||
s_logger.debug("NamingException while doing an LDAP bind", e);
|
||||
LOGGER.debug("NamingException while doing an LDAP bind", e);
|
||||
throw new InvalidParameterValueException("Unable to bind to the given LDAP server");
|
||||
} finally {
|
||||
closeContext(context);
|
||||
@ -142,12 +142,15 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
public boolean canAuthenticate(final String principal, final String password, final Long domainId) {
|
||||
try {
|
||||
// TODO return the right account for this user
|
||||
final LdapContext context = _ldapContextFactory.createUserContext(principal, password,domainId);
|
||||
final LdapContext context = _ldapContextFactory.createUserContext(principal, password, domainId);
|
||||
closeContext(context);
|
||||
if(LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace(String.format("User(%s) authenticated for domain(%s)", principal, domainId));
|
||||
}
|
||||
return true;
|
||||
} catch (NamingException | IOException e) {
|
||||
s_logger.debug("Exception while doing an LDAP bind for user "+" "+principal, e);
|
||||
s_logger.info("Failed to authenticate user: " + principal + ". incorrect password.");
|
||||
} catch (NamingException | IOException e) {/* AuthenticationException is caught as NamingException */
|
||||
LOGGER.debug("Exception while doing an LDAP bind for user "+" "+principal, e);
|
||||
LOGGER.info("Failed to authenticate user: " + principal + ". incorrect password.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -158,7 +161,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
context.close();
|
||||
}
|
||||
} catch (final NamingException e) {
|
||||
s_logger.warn(e.getMessage(), e);
|
||||
LOGGER.warn(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,7 +169,10 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
public LdapConfigurationResponse createLdapConfigurationResponse(final LdapConfigurationVO configuration) {
|
||||
String domainUuid = null;
|
||||
if(configuration.getDomainId() != null) {
|
||||
domainUuid = domainDao.findById(configuration.getDomainId()).getUuid();
|
||||
DomainVO domain = domainDao.findById(configuration.getDomainId());
|
||||
if (domain != null) {
|
||||
domainUuid = domain.getUuid();
|
||||
}
|
||||
}
|
||||
return new LdapConfigurationResponse(configuration.getHostname(), configuration.getPort(), domainUuid);
|
||||
}
|
||||
@ -199,7 +205,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
throw new InvalidParameterValueException("Cannot find configuration with hostname " + hostname);
|
||||
} else {
|
||||
_ldapConfigurationDao.remove(configuration.getId());
|
||||
s_logger.info("Removed ldap server with url: " + hostname + ':' + port + (domainId == null ? "" : " for domain id " + domainId));
|
||||
LOGGER.info("Removed ldap server with url: " + hostname + ':' + port + (domainId == null ? "" : " for domain id " + domainId));
|
||||
return createLdapConfigurationResponse(configuration);
|
||||
}
|
||||
}
|
||||
@ -231,7 +237,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUser(escapedUsername, context, domainId);
|
||||
|
||||
} catch (NamingException | IOException e) {
|
||||
s_logger.debug("ldap Exception: ",e);
|
||||
LOGGER.debug("ldap Exception: ",e);
|
||||
throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username);
|
||||
} finally {
|
||||
closeContext(context);
|
||||
@ -244,9 +250,15 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
try {
|
||||
context = _ldapContextFactory.createBindContext(domainId);
|
||||
final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username);
|
||||
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUser(escapedUsername, type, name, context, domainId);
|
||||
LdapUserManager.Provider ldapProvider = _ldapConfiguration.getLdapProvider(domainId);
|
||||
if (ldapProvider == null) {
|
||||
// feeble second attempt?
|
||||
ldapProvider = _ldapConfiguration.getLdapProvider(null);
|
||||
}
|
||||
LdapUserManager userManagerFactory = _ldapUserManagerFactory.getInstance(ldapProvider);
|
||||
return userManagerFactory.getUser(escapedUsername, type, name, context, domainId);
|
||||
} catch (NamingException | IOException e) {
|
||||
s_logger.debug("ldap Exception: ",e);
|
||||
LOGGER.debug("ldap Exception: ",e);
|
||||
throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username + " in group: " + name + " of type: " + type);
|
||||
} finally {
|
||||
closeContext(context);
|
||||
@ -260,7 +272,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
context = _ldapContextFactory.createBindContext(domainId);
|
||||
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsers(context, domainId);
|
||||
} catch (NamingException | IOException e) {
|
||||
s_logger.debug("ldap Exception: ",e);
|
||||
LOGGER.debug("ldap Exception: ",e);
|
||||
throw new NoLdapUserMatchingQueryException("*");
|
||||
} finally {
|
||||
closeContext(context);
|
||||
@ -274,7 +286,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
context = _ldapContextFactory.createBindContext(domainId);
|
||||
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsersInGroup(groupName, context, domainId);
|
||||
} catch (NamingException | IOException e) {
|
||||
s_logger.debug("ldap NamingException: ",e);
|
||||
LOGGER.debug("ldap NamingException: ",e);
|
||||
throw new NoLdapUserMatchingQueryException("groupName=" + groupName);
|
||||
} finally {
|
||||
closeContext(context);
|
||||
@ -286,6 +298,13 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
return listConfigurations(new LdapListConfigurationCmd(this)).second() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLdapEnabled(long domainId) {
|
||||
LdapListConfigurationCmd cmd = new LdapListConfigurationCmd(this);
|
||||
cmd.setDomainId(domainId);
|
||||
return listConfigurations(cmd).second() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<List<? extends LdapConfigurationVO>, Integer> listConfigurations(final LdapListConfigurationCmd cmd) {
|
||||
final String hostname = cmd.getHostname();
|
||||
@ -304,7 +323,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username);
|
||||
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUsers("*" + escapedUsername + "*", context, null);
|
||||
} catch (NamingException | IOException e) {
|
||||
s_logger.debug("ldap Exception: ",e);
|
||||
LOGGER.debug("ldap Exception: ",e);
|
||||
throw new NoLdapUserMatchingQueryException(username);
|
||||
} finally {
|
||||
closeContext(context);
|
||||
@ -313,9 +332,13 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
|
||||
@Override
|
||||
public LinkDomainToLdapResponse linkDomainToLdap(LinkDomainToLdapCmd cmd) {
|
||||
Validate.isTrue(_ldapConfiguration.getBaseDn(cmd.getDomainId()) == null, "can not link a domain unless a basedn is configured for it.");
|
||||
Validate.notEmpty(cmd.getLdapDomain(), "ldapDomain cannot be empty, please supply a GROUP or OU name");
|
||||
return linkDomainToLdap(cmd.getDomainId(),cmd.getType(),cmd.getLdapDomain(),cmd.getAccountType());
|
||||
final Long domainId = cmd.getDomainId();
|
||||
final String baseDn = _ldapConfiguration.getBaseDn(domainId);
|
||||
final String ldapDomain = cmd.getLdapDomain();
|
||||
|
||||
Validate.isTrue(baseDn != null, String.format("can not link a domain (with id = %d) unless a basedn (%s) is configured for it.", domainId, baseDn));
|
||||
Validate.notEmpty(ldapDomain, "ldapDomain cannot be empty, please supply a GROUP or OU name");
|
||||
return linkDomainToLdap(cmd.getDomainId(),cmd.getType(), ldapDomain,cmd.getAccountType());
|
||||
}
|
||||
|
||||
private LinkDomainToLdapResponse linkDomainToLdap(Long domainId, String type, String name, short accountType) {
|
||||
@ -329,7 +352,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
DomainVO domain = domainDao.findById(vo.getDomainId());
|
||||
String domainUuid = "<unknown>";
|
||||
if (domain == null) {
|
||||
s_logger.error("no domain in database for id " + vo.getDomainId());
|
||||
LOGGER.error("no domain in database for id " + vo.getDomainId());
|
||||
} else {
|
||||
domainUuid = domain.getUuid();
|
||||
}
|
||||
@ -371,12 +394,14 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
account = new AccountVO(cmd.getAccountName(), cmd.getDomainId(), null, cmd.getAccountType(), UUID.randomUUID().toString());
|
||||
accountDao.persist((AccountVO)account);
|
||||
}
|
||||
|
||||
Long accountId = account.getAccountId();
|
||||
clearOldAccountMapping(cmd);
|
||||
LdapTrustMapVO vo = _ldapTrustMapDao.persist(new LdapTrustMapVO(cmd.getDomainId(), linkType, cmd.getLdapDomain(), cmd.getAccountType(), accountId));
|
||||
DomainVO domain = domainDao.findById(vo.getDomainId());
|
||||
String domainUuid = "<unknown>";
|
||||
if (domain == null) {
|
||||
s_logger.error("no domain in database for id " + vo.getDomainId());
|
||||
LOGGER.error("no domain in database for id " + vo.getDomainId());
|
||||
} else {
|
||||
domainUuid = domain.getUuid();
|
||||
}
|
||||
@ -384,4 +409,29 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
|
||||
LinkAccountToLdapResponse response = new LinkAccountToLdapResponse(domainUuid, vo.getType().toString(), vo.getName(), vo.getAccountType(), account.getUuid(), cmd.getAccountName());
|
||||
return response;
|
||||
}
|
||||
|
||||
private void clearOldAccountMapping(LinkAccountToLdapCmd cmd) {
|
||||
// first find if exists log warning and update
|
||||
LdapTrustMapVO oldVo = _ldapTrustMapDao.findGroupInDomain(cmd.getDomainId(), cmd.getLdapDomain());
|
||||
if(oldVo != null) {
|
||||
// deal with edge cases, i.e. check if the old account is indeed deleted etc.
|
||||
if (oldVo.getAccountId() != 0l) {
|
||||
AccountVO oldAcount = accountDao.findByIdIncludingRemoved(oldVo.getAccountId());
|
||||
String msg = String.format("group %s is mapped to account %d in the current domain (%s)", cmd.getLdapDomain(), oldVo.getAccountId(), cmd.getDomainId());
|
||||
if (null == oldAcount.getRemoved()) {
|
||||
msg += ", delete the old map before mapping a new account to the same group.";
|
||||
LOGGER.error(msg);
|
||||
throw new CloudRuntimeException(msg);
|
||||
} else {
|
||||
msg += ", the old map is deleted.";
|
||||
LOGGER.warn(msg);
|
||||
_ldapTrustMapDao.expunge(oldVo.getId());
|
||||
}
|
||||
} else {
|
||||
String msg = String.format("group %s is mapped to the current domain (%s) for autoimport and can not be used for autosync", cmd.getLdapDomain(), cmd.getDomainId());
|
||||
LOGGER.error(msg);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ public class LdapUserManagerFactory implements ApplicationContextAware {
|
||||
|
||||
public static final Logger s_logger = Logger.getLogger(LdapUserManagerFactory.class.getName());
|
||||
|
||||
private static Map<LdapUserManager.Provider, LdapUserManager> ldapUserManagerMap = new HashMap<>();
|
||||
static Map<LdapUserManager.Provider, LdapUserManager> ldapUserManagerMap = new HashMap<>();
|
||||
|
||||
private ApplicationContext applicationCtx;
|
||||
|
||||
|
||||
@ -33,16 +33,20 @@ import javax.naming.ldap.LdapContext;
|
||||
import javax.naming.ldap.PagedResultsControl;
|
||||
import javax.naming.ldap.PagedResultsResponseControl;
|
||||
|
||||
import org.apache.cloudstack.ldap.dao.LdapTrustMapDao;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
public class OpenLdapUserManagerImpl implements LdapUserManager {
|
||||
private static final Logger s_logger = Logger.getLogger(OpenLdapUserManagerImpl.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(OpenLdapUserManagerImpl.class.getName());
|
||||
|
||||
@Inject
|
||||
protected LdapConfiguration _ldapConfiguration;
|
||||
|
||||
@Inject
|
||||
LdapTrustMapDao _ldapTrustMapDao;
|
||||
|
||||
public OpenLdapUserManagerImpl() {
|
||||
}
|
||||
|
||||
@ -82,25 +86,62 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
|
||||
usernameFilter.append((username == null ? "*" : username));
|
||||
usernameFilter.append(")");
|
||||
|
||||
final StringBuilder memberOfFilter = new StringBuilder();
|
||||
if (_ldapConfiguration.getSearchGroupPrinciple(domainId) != null) {
|
||||
if(s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("adding search filter for '" + _ldapConfiguration.getSearchGroupPrinciple(domainId) +
|
||||
"', using " + _ldapConfiguration.getUserMemberOfAttribute(domainId));
|
||||
String memberOfAttribute = _ldapConfiguration.getUserMemberOfAttribute(domainId);
|
||||
StringBuilder ldapGroupsFilter = new StringBuilder();
|
||||
// this should get the trustmaps for this domain
|
||||
List<String> ldapGroups = getMappedLdapGroups(domainId);
|
||||
if (null != ldapGroups && ldapGroups.size() > 0) {
|
||||
ldapGroupsFilter.append("(|");
|
||||
for (String ldapGroup : ldapGroups) {
|
||||
ldapGroupsFilter.append(getMemberOfGroupString(ldapGroup, memberOfAttribute));
|
||||
}
|
||||
memberOfFilter.append("(" + _ldapConfiguration.getUserMemberOfAttribute(domainId) + "=");
|
||||
memberOfFilter.append(_ldapConfiguration.getSearchGroupPrinciple(domainId));
|
||||
memberOfFilter.append(")");
|
||||
ldapGroupsFilter.append(')');
|
||||
}
|
||||
// make sure only users in the principle group are retrieved
|
||||
String pricipleGroup = _ldapConfiguration.getSearchGroupPrinciple(domainId);
|
||||
final StringBuilder principleGroupFilter = new StringBuilder();
|
||||
if (null != pricipleGroup) {
|
||||
principleGroupFilter.append(getMemberOfGroupString(pricipleGroup, memberOfAttribute));
|
||||
}
|
||||
|
||||
final StringBuilder result = new StringBuilder();
|
||||
result.append("(&");
|
||||
result.append(userObjectFilter);
|
||||
result.append(usernameFilter);
|
||||
result.append(memberOfFilter);
|
||||
result.append(ldapGroupsFilter);
|
||||
result.append(principleGroupFilter);
|
||||
result.append(")");
|
||||
|
||||
return result.toString();
|
||||
String returnString = result.toString();
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("constructed ldap query: " + returnString);
|
||||
}
|
||||
return returnString;
|
||||
}
|
||||
|
||||
private List<String> getMappedLdapGroups(Long domainId) {
|
||||
List <String> ldapGroups = new ArrayList<>();
|
||||
// first get the trustmaps
|
||||
if (null != domainId) {
|
||||
for (LdapTrustMapVO trustMap : _ldapTrustMapDao.searchByDomainId(domainId)) {
|
||||
// then retrieve the string from it
|
||||
ldapGroups.add(trustMap.getName());
|
||||
}
|
||||
}
|
||||
return ldapGroups;
|
||||
}
|
||||
|
||||
private String getMemberOfGroupString(String group, String memberOfAttribute) {
|
||||
final StringBuilder memberOfFilter = new StringBuilder();
|
||||
if (null != group) {
|
||||
if(LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("adding search filter for '" + group +
|
||||
"', using '" + memberOfAttribute + "'");
|
||||
}
|
||||
memberOfFilter.append("(" + memberOfAttribute + "=");
|
||||
memberOfFilter.append(group);
|
||||
memberOfFilter.append(")");
|
||||
}
|
||||
return memberOfFilter.toString();
|
||||
}
|
||||
|
||||
private String generateGroupSearchFilter(final String groupName, Long domainId) {
|
||||
@ -212,7 +253,7 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
|
||||
try{
|
||||
users.add(getUserForDn(userdn, context, domainId));
|
||||
} catch (NamingException e){
|
||||
s_logger.info("Userdn: " + userdn + " Not Found:: Exception message: " + e.getMessage());
|
||||
LOGGER.info("Userdn: " + userdn + " Not Found:: Exception message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -251,8 +292,8 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
|
||||
searchControls.setReturningAttributes(_ldapConfiguration.getReturnAttributes(domainId));
|
||||
|
||||
NamingEnumeration<SearchResult> results = context.search(basedn, searchString, searchControls);
|
||||
if(s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("searching user(s) with filter: \"" + searchString + "\"");
|
||||
if(LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("searching user(s) with filter: \"" + searchString + "\"");
|
||||
}
|
||||
final List<LdapUser> users = new ArrayList<LdapUser>();
|
||||
while (results.hasMoreElements()) {
|
||||
@ -277,7 +318,7 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
|
||||
|
||||
String basedn = _ldapConfiguration.getBaseDn(domainId);
|
||||
if (StringUtils.isBlank(basedn)) {
|
||||
throw new IllegalArgumentException("ldap basedn is not configured");
|
||||
throw new IllegalArgumentException(String.format("ldap basedn is not configured (for domain: %s)", domainId));
|
||||
}
|
||||
byte[] cookie = null;
|
||||
int pageSize = _ldapConfiguration.getLdapPageSize(domainId);
|
||||
@ -301,7 +342,7 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s_logger.info("No controls were sent from the ldap server");
|
||||
LOGGER.info("No controls were sent from the ldap server");
|
||||
}
|
||||
context.setRequestControls(new Control[] {new PagedResultsControl(pageSize, cookie, Control.CRITICAL)});
|
||||
} while (cookie != null);
|
||||
|
||||
@ -75,7 +75,7 @@ public class LdapConfigurationDaoImpl extends GenericDaoBase<LdapConfigurationVO
|
||||
private SearchCriteria<LdapConfigurationVO> getSearchCriteria(String hostname, int port, Long domainId) {
|
||||
SearchCriteria<LdapConfigurationVO> sc;
|
||||
if (domainId == null) {
|
||||
sc = listDomainConfigurationsSearch.create();
|
||||
sc = listGlobalConfigurationsSearch.create();
|
||||
} else {
|
||||
sc = listDomainConfigurationsSearch.create();
|
||||
sc.setParameters("domain_id", domainId);
|
||||
|
||||
@ -0,0 +1,466 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.cloudstack.api.command;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.domain.DomainVO;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountVO;
|
||||
import com.cloud.user.DomainService;
|
||||
import com.cloud.user.User;
|
||||
import org.apache.cloudstack.api.ResponseObject;
|
||||
import org.apache.cloudstack.api.response.LdapUserResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.UserResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.ldap.LdapManager;
|
||||
import org.apache.cloudstack.ldap.LdapUser;
|
||||
import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException;
|
||||
import org.apache.cloudstack.query.QueryService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyBoolean;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.powermock.api.mockito.PowerMockito.doReturn;
|
||||
import static org.powermock.api.mockito.PowerMockito.doThrow;
|
||||
import static org.powermock.api.mockito.PowerMockito.spy;
|
||||
import static org.powermock.api.mockito.PowerMockito.when;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest(CallContext.class)
|
||||
public class LdapListUsersCmdTest implements LdapConfigurationChanger {
|
||||
|
||||
public static final String LOCAL_DOMAIN_ID = "12345678-90ab-cdef-fedc-ba0987654321";
|
||||
public static final String LOCAL_DOMAIN_NAME = "engineering";
|
||||
@Mock
|
||||
LdapManager ldapManager;
|
||||
@Mock
|
||||
QueryService queryService;
|
||||
@Mock
|
||||
DomainService domainService;
|
||||
|
||||
LdapListUsersCmd ldapListUsersCmd;
|
||||
LdapListUsersCmd cmdSpy;
|
||||
|
||||
Domain localDomain;
|
||||
|
||||
@Before
|
||||
public void setUp() throws NoSuchFieldException, IllegalAccessException {
|
||||
ldapListUsersCmd = new LdapListUsersCmd(ldapManager, queryService);
|
||||
cmdSpy = spy(ldapListUsersCmd);
|
||||
|
||||
PowerMockito.mockStatic(CallContext.class);
|
||||
CallContext callContextMock = PowerMockito.mock(CallContext.class);
|
||||
PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
|
||||
Account accountMock = PowerMockito.mock(Account.class);
|
||||
PowerMockito.when(accountMock.getDomainId()).thenReturn(1l);
|
||||
PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
|
||||
|
||||
ldapListUsersCmd._domainService = domainService;
|
||||
|
||||
// no need to setHiddenField(ldapListUsersCmd, .... );
|
||||
}
|
||||
|
||||
/**
|
||||
* given: "We have an LdapManager, QueryService and LdapListUsersCmd"
|
||||
* when: "Get entity owner id is called"
|
||||
* then: "a 1 should be returned"
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void getEntityOwnerIdisOne() {
|
||||
long ownerId = ldapListUsersCmd.getEntityOwnerId();
|
||||
assertEquals(ownerId, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* given: "We have an LdapManager with no users, QueryService and a LdapListUsersCmd"
|
||||
* when: "LdapListUsersCmd is executed"
|
||||
* then: "An array of size 0 is returned"
|
||||
*
|
||||
* @throws NoLdapUserMatchingQueryException
|
||||
*/
|
||||
@Test
|
||||
public void successfulEmptyResponseFromExecute() throws NoLdapUserMatchingQueryException {
|
||||
doThrow(new NoLdapUserMatchingQueryException("")).when(ldapManager).getUsers(null);
|
||||
ldapListUsersCmd.execute();
|
||||
assertEquals(0, ((ListResponse)ldapListUsersCmd.getResponseObject()).getResponses().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* given: "We have an LdapManager, one user, QueryService and a LdapListUsersCmd"
|
||||
* when: "LdapListUsersCmd is executed"
|
||||
* then: "a list of size not 0 is returned"
|
||||
*/
|
||||
@Test
|
||||
public void successfulResponseFromExecute() throws NoLdapUserMatchingQueryException {
|
||||
mockACSUserSearch();
|
||||
|
||||
mockResponseCreation();
|
||||
|
||||
useSubdomain();
|
||||
|
||||
ldapListUsersCmd.execute();
|
||||
|
||||
verify(queryService, times(1)).searchForUsers(anyLong(), anyBoolean());
|
||||
assertNotEquals(0, ((ListResponse)ldapListUsersCmd.getResponseObject()).getResponses().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* given: "We have an LdapManager, QueryService and a LdapListUsersCmd"
|
||||
* when: "Get command name is called"
|
||||
* then: "ldapuserresponse is returned"
|
||||
*/
|
||||
@Test
|
||||
public void successfulReturnOfCommandName() {
|
||||
String commandName = ldapListUsersCmd.getCommandName();
|
||||
|
||||
assertEquals("ldapuserresponse", commandName);
|
||||
}
|
||||
|
||||
/**
|
||||
* given: "We have an LdapUser and a CloudStack user whose username match"
|
||||
* when: "isACloudstackUser is executed"
|
||||
* then: "The result is true"
|
||||
*
|
||||
* TODO: is this really the valid behaviour? shouldn't the user also be linked to ldap and not accidentally match?
|
||||
*/
|
||||
@Test
|
||||
public void isACloudstackUser() {
|
||||
mockACSUserSearch();
|
||||
|
||||
LdapUser ldapUser = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null, false, null);
|
||||
|
||||
boolean result = ldapListUsersCmd.isACloudstackUser(ldapUser);
|
||||
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* given: "We have an LdapUser and not a matching CloudstackUser"
|
||||
* when: "isACloudstackUser is executed"
|
||||
* then: "The result is false"
|
||||
*/
|
||||
@Test
|
||||
public void isNotACloudstackUser() {
|
||||
doReturn(new ListResponse<UserResponse>()).when(queryService).searchForUsers(anyLong(), anyBoolean());
|
||||
|
||||
LdapUser ldapUser = new LdapUser("rmurphy", "rmurphy@cloudstack.org", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null, false, null);
|
||||
|
||||
boolean result = ldapListUsersCmd.isACloudstackUser(ldapUser);
|
||||
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* test whether a value other than 'any' for 'listtype' leads to a good 'userfilter' value
|
||||
*/
|
||||
@Test
|
||||
public void getListtypeOther() {
|
||||
when(cmdSpy.getListTypeString()).thenReturn("otHer", "anY");
|
||||
|
||||
String userfilter = cmdSpy.getUserFilterString();
|
||||
assertEquals("AnyDomain", userfilter);
|
||||
|
||||
userfilter = cmdSpy.getUserFilterString();
|
||||
assertEquals("AnyDomain", userfilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* test whether a value of 'any' for 'listtype' leads to a good 'userfilter' value
|
||||
*/
|
||||
@Test
|
||||
public void getListtypeAny() {
|
||||
when(cmdSpy.getListTypeString()).thenReturn("all");
|
||||
String userfilter = cmdSpy.getUserFilterString();
|
||||
assertEquals("NoFilter", userfilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* test whether values for 'userfilter' yield the right filter
|
||||
*/
|
||||
@Test
|
||||
public void getUserFilter() throws NoSuchFieldException, IllegalAccessException {
|
||||
when(cmdSpy.getListTypeString()).thenReturn("otHer");
|
||||
LdapListUsersCmd.UserFilter userfilter = cmdSpy.getUserFilter();
|
||||
|
||||
assertEquals(LdapListUsersCmd.UserFilter.ANY_DOMAIN, userfilter);
|
||||
|
||||
when(cmdSpy.getListTypeString()).thenReturn("anY");
|
||||
userfilter = cmdSpy.getUserFilter();
|
||||
assertEquals(LdapListUsersCmd.UserFilter.ANY_DOMAIN, userfilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* test if the right exception is thrown on invalid input.
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getInvalidUserFilterValues() throws NoSuchFieldException, IllegalAccessException {
|
||||
setHiddenField(ldapListUsersCmd, "userFilter", "flase");
|
||||
// unused output: LdapListUsersCmd.UserFilter userfilter =
|
||||
ldapListUsersCmd.getUserFilter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUserFilterValues() {
|
||||
assertEquals("PotentialImport", LdapListUsersCmd.UserFilter.POTENTIAL_IMPORT.toString());
|
||||
assertEquals(LdapListUsersCmd.UserFilter.POTENTIAL_IMPORT, LdapListUsersCmd.UserFilter.fromString("PotentialImport"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getInvalidUserFilterStringValue() {
|
||||
LdapListUsersCmd.UserFilter.fromString("PotentImport");
|
||||
}
|
||||
|
||||
/**
|
||||
* apply no filter
|
||||
*
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@Test
|
||||
public void applyNoFilter() throws NoSuchFieldException, IllegalAccessException, NoLdapUserMatchingQueryException {
|
||||
mockACSUserSearch();
|
||||
mockResponseCreation();
|
||||
|
||||
useSubdomain();
|
||||
|
||||
setHiddenField(ldapListUsersCmd, "userFilter", "NoFilter");
|
||||
ldapListUsersCmd.execute();
|
||||
|
||||
assertEquals(3, ((ListResponse)ldapListUsersCmd.getResponseObject()).getResponses().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* filter all acs users
|
||||
*
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@Test
|
||||
public void applyAnyDomain() throws NoSuchFieldException, IllegalAccessException, NoLdapUserMatchingQueryException {
|
||||
mockACSUserSearch();
|
||||
mockResponseCreation();
|
||||
|
||||
useSubdomain();
|
||||
|
||||
setHiddenField(ldapListUsersCmd, "userFilter", "AnyDomain");
|
||||
setHiddenField(ldapListUsersCmd, "domainId", 2l /* not root */);
|
||||
ldapListUsersCmd.execute();
|
||||
|
||||
// 'rmurphy' annotated with native
|
||||
// 'bob' still in
|
||||
// 'abhi' is filtered out
|
||||
List<ResponseObject> responses = ((ListResponse)ldapListUsersCmd.getResponseObject()).getResponses();
|
||||
assertEquals(2, responses.size());
|
||||
for(ResponseObject response : responses) {
|
||||
if(!(response instanceof LdapUserResponse)) {
|
||||
fail("unexpected return-type from API backend method");
|
||||
} else {
|
||||
LdapUserResponse userResponse = (LdapUserResponse)response;
|
||||
// further validate this user
|
||||
if ("rmurphy".equals(userResponse.getUsername()) &&
|
||||
! User.Source.NATIVE.toString().equalsIgnoreCase(userResponse.getUserSource())) {
|
||||
fail("expected murphy from ldap");
|
||||
}
|
||||
if ("bob".equals(userResponse.getUsername()) &&
|
||||
! "".equals(userResponse.getUserSource())) {
|
||||
fail("expected bob from without usersource");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* filter out acs users for the requested domain
|
||||
*
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@Test
|
||||
public void applyLocalDomainForASubDomain() throws NoSuchFieldException, IllegalAccessException, NoLdapUserMatchingQueryException {
|
||||
mockACSUserSearch();
|
||||
mockResponseCreation();
|
||||
|
||||
setHiddenField(ldapListUsersCmd, "userFilter", "LocalDomain");
|
||||
setHiddenField(ldapListUsersCmd, "domainId", 2l /* not root */);
|
||||
|
||||
localDomain = useSubdomain();
|
||||
|
||||
ldapListUsersCmd.execute();
|
||||
|
||||
// 'rmurphy' filtered out 'bob' still in
|
||||
assertEquals(2, ((ListResponse)ldapListUsersCmd.getResponseObject()).getResponses().size());
|
||||
// todo: assert user sources
|
||||
}
|
||||
|
||||
/**
|
||||
* filter out acs users for the default domain
|
||||
*
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@Test
|
||||
public void applyLocalDomainForTheCallersDomain() throws NoSuchFieldException, IllegalAccessException, NoLdapUserMatchingQueryException {
|
||||
mockACSUserSearch();
|
||||
mockResponseCreation();
|
||||
|
||||
setHiddenField(ldapListUsersCmd, "userFilter", "LocalDomain");
|
||||
|
||||
AccountVO account = new AccountVO();
|
||||
setHiddenField(account, "accountName", "admin");
|
||||
setHiddenField(account, "domainId", 1l);
|
||||
final CallContext callContext = CallContext.current();
|
||||
setHiddenField(callContext, "account", account);
|
||||
DomainVO domainVO = useDomain("ROOT", 1l);
|
||||
localDomain = domainVO;
|
||||
|
||||
ldapListUsersCmd.execute();
|
||||
|
||||
// 'rmurphy' filtered out 'bob' still in
|
||||
assertEquals(2, ((ListResponse)ldapListUsersCmd.getResponseObject()).getResponses().size());
|
||||
// todo: assert usersources
|
||||
}
|
||||
|
||||
/**
|
||||
* todo generate an extensive configuration and check with an extensive user list
|
||||
*
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@Test
|
||||
public void applyPotentialImport() throws NoSuchFieldException, IllegalAccessException, NoLdapUserMatchingQueryException {
|
||||
mockACSUserSearch();
|
||||
mockResponseCreation();
|
||||
|
||||
useSubdomain();
|
||||
|
||||
setHiddenField(ldapListUsersCmd, "userFilter", "PotentialImport");
|
||||
ldapListUsersCmd.execute();
|
||||
|
||||
assertEquals(2, ((ListResponse)ldapListUsersCmd.getResponseObject()).getResponses().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* unknown filter
|
||||
*
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void applyUnknownFilter() throws NoSuchFieldException, IllegalAccessException {
|
||||
setHiddenField(ldapListUsersCmd, "userFilter", "UnknownFilter");
|
||||
ldapListUsersCmd.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* make sure there are no unimplemented filters
|
||||
*
|
||||
* This was created to deal with the possible {code}NoSuchMethodException{code} that won't be dealt with in regular coverage
|
||||
*
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@Test
|
||||
public void applyUnimplementedFilter() throws NoSuchFieldException, IllegalAccessException {
|
||||
useSubdomain();
|
||||
for (LdapListUsersCmd.UserFilter UNIMPLEMENTED_FILTER : LdapListUsersCmd.UserFilter.values()) {
|
||||
setHiddenField(ldapListUsersCmd, "userFilter", UNIMPLEMENTED_FILTER.toString());
|
||||
ldapListUsersCmd.getUserFilter().filter(ldapListUsersCmd,new ArrayList<LdapUserResponse>());
|
||||
}
|
||||
}
|
||||
|
||||
// helper methods //
|
||||
////////////////////
|
||||
private DomainVO useSubdomain() {
|
||||
DomainVO domainVO = useDomain(LOCAL_DOMAIN_NAME, 2l);
|
||||
return domainVO;
|
||||
}
|
||||
|
||||
private DomainVO useDomain(String domainName, long domainId) {
|
||||
DomainVO domainVO = new DomainVO();
|
||||
domainVO.setName(domainName);
|
||||
domainVO.setId(domainId);
|
||||
domainVO.setUuid(LOCAL_DOMAIN_ID);
|
||||
when(domainService.getDomain(anyLong())).thenReturn(domainVO);
|
||||
return domainVO;
|
||||
}
|
||||
|
||||
private void mockACSUserSearch() {
|
||||
UserResponse rmurphy = createMockUserResponse("rmurphy", User.Source.NATIVE);
|
||||
UserResponse rohit = createMockUserResponse("rohit", User.Source.SAML2);
|
||||
UserResponse abhi = createMockUserResponse("abhi", User.Source.LDAP);
|
||||
|
||||
ArrayList<UserResponse> responses = new ArrayList<>();
|
||||
responses.add(rmurphy);
|
||||
responses.add(rohit);
|
||||
responses.add(abhi);
|
||||
|
||||
ListResponse<UserResponse> queryServiceResponse = new ListResponse<>();
|
||||
queryServiceResponse.setResponses(responses);
|
||||
|
||||
doReturn(queryServiceResponse).when(queryService).searchForUsers(anyLong(), anyBoolean());
|
||||
}
|
||||
|
||||
private UserResponse createMockUserResponse(String uid, User.Source source) {
|
||||
UserResponse userResponse = new UserResponse();
|
||||
userResponse.setUsername(uid);
|
||||
userResponse.setUserSource(source);
|
||||
|
||||
// for now:
|
||||
userResponse.setDomainId(LOCAL_DOMAIN_ID);
|
||||
userResponse.setDomainName(LOCAL_DOMAIN_NAME);
|
||||
|
||||
return userResponse;
|
||||
}
|
||||
|
||||
private void mockResponseCreation() throws NoLdapUserMatchingQueryException {
|
||||
List<LdapUser> users = new ArrayList();
|
||||
LdapUser murphy = new LdapUser("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", "mythical", false, null);
|
||||
LdapUser bob = new LdapUser("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", LOCAL_DOMAIN_NAME, false, null);
|
||||
LdapUser abhi = new LdapUser("abhi", "abhi@test.com", "Abhi", "YoungOrOld", "cn=abhi,ou=engineering,dc=cloudstack,dc=org", LOCAL_DOMAIN_NAME, false, null);
|
||||
users.add(murphy);
|
||||
users.add(bob);
|
||||
users.add(abhi);
|
||||
|
||||
doReturn(users).when(ldapManager).getUsers(any());
|
||||
|
||||
LdapUserResponse response = new LdapUserResponse("rmurphy", "rmurphy@test.com", "Ryan", "Murphy", "cn=rmurphy,dc=cloudstack,dc=org", null);
|
||||
doReturn(response).when(ldapManager).createLdapUserResponse(murphy);
|
||||
LdapUserResponse bobResponse = new LdapUserResponse("bob", "bob@test.com", "Robert", "Young", "cn=bob,ou=engineering,dc=cloudstack,dc=org", LOCAL_DOMAIN_NAME);
|
||||
doReturn(bobResponse).when(ldapManager).createLdapUserResponse(bob);
|
||||
LdapUserResponse abhiResponse = new LdapUserResponse("abhi", "abhi@test.com", "Abhi", "YoungOrOld", "cn=abhi,ou=engineering,dc=cloudstack,dc=org", LOCAL_DOMAIN_NAME);
|
||||
doReturn(abhiResponse).when(ldapManager).createLdapUserResponse(abhi);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,326 @@
|
||||
/*-
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you 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 org.apache.cloudstack.ldap;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.directory.api.ldap.model.entry.Entry;
|
||||
import org.apache.directory.api.ldap.model.exception.LdapException;
|
||||
import org.apache.directory.api.ldap.model.schema.registries.DefaultSchema;
|
||||
import org.apache.directory.api.ldap.model.schema.registries.Schema;
|
||||
import org.apache.directory.api.ldap.schema.loader.JarLdifSchemaLoader;
|
||||
import org.apache.directory.api.ldap.schema.loader.LdifSchemaLoader;
|
||||
import org.apache.directory.server.core.api.CoreSession;
|
||||
import org.apache.directory.server.core.api.DirectoryService;
|
||||
import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory;
|
||||
import org.apache.directory.server.core.factory.JdbmPartitionFactory;
|
||||
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
|
||||
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
|
||||
import org.apache.directory.server.ldap.LdapServer;
|
||||
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
|
||||
import org.apache.directory.server.xdbm.Index;
|
||||
import org.apache.directory.server.xdbm.IndexNotFoundException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Call init() to start the server and destroy() to shut it down.
|
||||
*/
|
||||
public class EmbeddedLdapServer {
|
||||
// API References:
|
||||
// http://directory.apache.org/apacheds/gen-docs/latest/apidocs/
|
||||
// http://directory.apache.org/api/gen-docs/latest/apidocs/
|
||||
|
||||
private static final String BASE_PARTITION_NAME = "mydomain";
|
||||
private static final String BASE_DOMAIN = "org";
|
||||
private static final String BASE_STRUCTURE = "dc=" + BASE_PARTITION_NAME + ",dc=" + BASE_DOMAIN;
|
||||
|
||||
private static final int LDAP_SERVER_PORT = 10389;
|
||||
private static final int BASE_CACHE_SIZE = 1000;
|
||||
private static final List<String> ATTR_NAMES_TO_INDEX = new ArrayList<String>(Arrays.asList("uid"));
|
||||
|
||||
private DirectoryService _directoryService;
|
||||
private LdapServer _ldapServer;
|
||||
private JdbmPartition _basePartition;
|
||||
private boolean _deleteInstanceDirectoryOnStartup = true;
|
||||
private boolean _deleteInstanceDirectoryOnShutdown = true;
|
||||
|
||||
public String getBasePartitionName() {
|
||||
return BASE_PARTITION_NAME;
|
||||
}
|
||||
|
||||
public String getBaseStructure() {
|
||||
return BASE_STRUCTURE;
|
||||
}
|
||||
|
||||
public int getBaseCacheSize() {
|
||||
return BASE_CACHE_SIZE;
|
||||
}
|
||||
|
||||
public int getLdapServerPort() {
|
||||
return LDAP_SERVER_PORT;
|
||||
}
|
||||
|
||||
public List<String> getAttrNamesToIndex() {
|
||||
return ATTR_NAMES_TO_INDEX;
|
||||
}
|
||||
|
||||
protected void addSchemaExtensions() throws LdapException, IOException {
|
||||
// override to add custom attributes to the schema
|
||||
}
|
||||
|
||||
public void init() throws Exception {
|
||||
if (getDirectoryService() == null) {
|
||||
if (getDeleteInstanceDirectoryOnStartup()) {
|
||||
deleteDirectory(getGuessedInstanceDirectory());
|
||||
}
|
||||
|
||||
DefaultDirectoryServiceFactory serviceFactory = new DefaultDirectoryServiceFactory();
|
||||
serviceFactory.init(getDirectoryServiceName());
|
||||
setDirectoryService(serviceFactory.getDirectoryService());
|
||||
|
||||
getDirectoryService().getChangeLog().setEnabled(false);
|
||||
getDirectoryService().setDenormalizeOpAttrsEnabled(true);
|
||||
|
||||
createBasePartition();
|
||||
|
||||
getDirectoryService().startup();
|
||||
|
||||
createRootEntry();
|
||||
}
|
||||
|
||||
if (getLdapServer() == null) {
|
||||
setLdapServer(new LdapServer());
|
||||
getLdapServer().setDirectoryService(getDirectoryService());
|
||||
getLdapServer().setTransports(new TcpTransport(getLdapServerPort()));
|
||||
getLdapServer().start();
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() throws Exception {
|
||||
File instanceDirectory = getDirectoryService().getInstanceLayout().getInstanceDirectory();
|
||||
getLdapServer().stop();
|
||||
getDirectoryService().shutdown();
|
||||
setLdapServer(null);
|
||||
setDirectoryService(null);
|
||||
if (getDeleteInstanceDirectoryOnShutdown()) {
|
||||
deleteDirectory(instanceDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
public String getDirectoryServiceName() {
|
||||
return getBasePartitionName() + "DirectoryService";
|
||||
}
|
||||
|
||||
private static void deleteDirectory(File path) throws IOException {
|
||||
FileUtils.deleteDirectory(path);
|
||||
}
|
||||
|
||||
protected void createBasePartition() throws Exception {
|
||||
JdbmPartitionFactory jdbmPartitionFactory = new JdbmPartitionFactory();
|
||||
setBasePartition(jdbmPartitionFactory.createPartition(getDirectoryService().getSchemaManager(), getDirectoryService().getDnFactory(), getBasePartitionName(), getBaseStructure(), getBaseCacheSize(), getBasePartitionPath()));
|
||||
addSchemaExtensions();
|
||||
createBaseIndices();
|
||||
getDirectoryService().addPartition(getBasePartition());
|
||||
}
|
||||
|
||||
protected void createBaseIndices() throws Exception {
|
||||
//
|
||||
// Default indices, that can be seen with getSystemIndexMap() and
|
||||
// getUserIndexMap(), are minimal. There are no user indices by
|
||||
// default and the default system indices are:
|
||||
//
|
||||
// apacheOneAlias, entryCSN, apacheSubAlias, apacheAlias,
|
||||
// objectClass, apachePresence, apacheRdn, administrativeRole
|
||||
//
|
||||
for (String attrName : getAttrNamesToIndex()) {
|
||||
getBasePartition().addIndex(createIndexObjectForAttr(attrName));
|
||||
}
|
||||
}
|
||||
|
||||
protected JdbmIndex<?> createIndexObjectForAttr(String attrName, boolean withReverse) throws LdapException {
|
||||
String oid = getOidByAttributeName(attrName);
|
||||
if (oid == null) {
|
||||
throw new RuntimeException("OID could not be found for attr " + attrName);
|
||||
}
|
||||
return new JdbmIndex(oid, withReverse);
|
||||
}
|
||||
|
||||
protected JdbmIndex<?> createIndexObjectForAttr(String attrName) throws LdapException {
|
||||
return createIndexObjectForAttr(attrName, false);
|
||||
}
|
||||
|
||||
protected void createRootEntry() throws LdapException {
|
||||
Entry entry = getDirectoryService().newEntry(getDirectoryService().getDnFactory().create(getBaseStructure()));
|
||||
entry.add("objectClass", "top", "domain", "extensibleObject");
|
||||
entry.add("dc", getBasePartitionName());
|
||||
CoreSession session = getDirectoryService().getAdminSession();
|
||||
try {
|
||||
session.add(entry);
|
||||
} finally {
|
||||
session.unbind();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A map where the key is the attribute name the value is the
|
||||
* oid.
|
||||
*/
|
||||
public Map<String, String> getSystemIndexMap() throws IndexNotFoundException {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
Iterator<String> it = getBasePartition().getSystemIndices();
|
||||
while (it.hasNext()) {
|
||||
String oid = it.next();
|
||||
Index<?, String> index = getBasePartition().getSystemIndex(getDirectoryService().getSchemaManager().getAttributeType(oid));
|
||||
result.put(index.getAttribute().getName(), index.getAttributeId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A map where the key is the attribute name the value is the
|
||||
* oid.
|
||||
*/
|
||||
public Map<String, String> getUserIndexMap() throws IndexNotFoundException {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
Iterator<String> it = getBasePartition().getUserIndices();
|
||||
while (it.hasNext()) {
|
||||
String oid = it.next();
|
||||
Index<?, String> index = getBasePartition().getUserIndex(getDirectoryService().getSchemaManager().getAttributeType(oid));
|
||||
result.put(index.getAttribute().getName(), index.getAttributeId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public File getPartitionsDirectory() {
|
||||
return getDirectoryService().getInstanceLayout().getPartitionsDirectory();
|
||||
}
|
||||
|
||||
public File getBasePartitionPath() {
|
||||
return new File(getPartitionsDirectory(), getBasePartitionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used at init time to clear out the likely instance directory before
|
||||
* anything is created.
|
||||
*/
|
||||
public File getGuessedInstanceDirectory() {
|
||||
// See source code for DefaultDirectoryServiceFactory
|
||||
// buildInstanceDirectory. ApacheDS looks at the workingDirectory
|
||||
// system property first and then defers to the java.io.tmpdir
|
||||
// system property.
|
||||
final String property = System.getProperty("workingDirectory");
|
||||
return new File(property != null ? property : System.getProperty("java.io.tmpdir") + File.separator + "server-work-" + getDirectoryServiceName());
|
||||
}
|
||||
|
||||
public String getOidByAttributeName(String attrName) throws LdapException {
|
||||
return getDirectoryService().getSchemaManager().getAttributeTypeRegistry().getOidByName(attrName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional schemas to the directory server. This takes a path to
|
||||
* the schema directory and uses the LdifSchemaLoader.
|
||||
*
|
||||
* @param schemaLocation The path to the directory containing the
|
||||
* "ou=schema" directory for an additional schema
|
||||
* @param schemaName The name of the schema
|
||||
* @return true if the schemas have been loaded and the registries is
|
||||
* consistent
|
||||
*/
|
||||
public boolean addSchemaFromPath(File schemaLocation, String schemaName) throws LdapException, IOException {
|
||||
LdifSchemaLoader schemaLoader = new LdifSchemaLoader(schemaLocation);
|
||||
DefaultSchema schema = new DefaultSchema(schemaLoader, schemaName);
|
||||
return getDirectoryService().getSchemaManager().load(schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional schemas to the directory server. This uses
|
||||
* JarLdifSchemaLoader, which will search for the "ou=schema" directory
|
||||
* within "/schema" on the classpath. If packaging the schema as part of
|
||||
* a jar using Gradle or Maven, you'd probably want to put your
|
||||
* "ou=schema" directory in src/main/resources/schema.
|
||||
* <p/>
|
||||
* It's also required that a META-INF/apacheds-schema.index be present in
|
||||
* your classpath that lists each LDIF file in your schema directory.
|
||||
*
|
||||
* @param schemaName The name of the schema
|
||||
* @return true if the schemas have been loaded and the registries is
|
||||
* consistent
|
||||
*/
|
||||
public boolean addSchemaFromClasspath(String schemaName) throws LdapException, IOException {
|
||||
// To debug if your apacheds-schema.index isn't found:
|
||||
// Enumeration<URL> indexes = getClass().getClassLoader().getResources("META-INF/apacheds-schema.index");
|
||||
JarLdifSchemaLoader schemaLoader = new JarLdifSchemaLoader();
|
||||
Schema schema = schemaLoader.getSchema(schemaName);
|
||||
return schema != null && getDirectoryService().getSchemaManager().load(schema);
|
||||
}
|
||||
|
||||
public DirectoryService getDirectoryService() {
|
||||
return _directoryService;
|
||||
}
|
||||
|
||||
public void setDirectoryService(DirectoryService directoryService) {
|
||||
this._directoryService = directoryService;
|
||||
}
|
||||
|
||||
public LdapServer getLdapServer() {
|
||||
return _ldapServer;
|
||||
}
|
||||
|
||||
public void setLdapServer(LdapServer ldapServer) {
|
||||
this._ldapServer = ldapServer;
|
||||
}
|
||||
|
||||
public JdbmPartition getBasePartition() {
|
||||
return _basePartition;
|
||||
}
|
||||
|
||||
public void setBasePartition(JdbmPartition basePartition) {
|
||||
this._basePartition = basePartition;
|
||||
}
|
||||
|
||||
public boolean getDeleteInstanceDirectoryOnStartup() {
|
||||
return _deleteInstanceDirectoryOnStartup;
|
||||
}
|
||||
|
||||
public void setDeleteInstanceDirectoryOnStartup(boolean deleteInstanceDirectoryOnStartup) {
|
||||
this._deleteInstanceDirectoryOnStartup = deleteInstanceDirectoryOnStartup;
|
||||
}
|
||||
|
||||
public boolean getDeleteInstanceDirectoryOnShutdown() {
|
||||
return _deleteInstanceDirectoryOnShutdown;
|
||||
}
|
||||
|
||||
public void setDeleteInstanceDirectoryOnShutdown(boolean deleteInstanceDirectoryOnShutdown) {
|
||||
this._deleteInstanceDirectoryOnShutdown = deleteInstanceDirectoryOnShutdown;
|
||||
}
|
||||
|
||||
public static void main (String[] args) {
|
||||
EmbeddedLdapServer embeddedLdapServer = new EmbeddedLdapServer();
|
||||
try {
|
||||
embeddedLdapServer.init();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,9 @@ package org.apache.cloudstack.ldap;
|
||||
|
||||
|
||||
import com.cloud.server.auth.UserAuthenticator;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.user.AccountVO;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.user.UserAccount;
|
||||
import com.cloud.user.UserAccountVO;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
@ -25,13 +28,20 @@ import com.cloud.utils.Pair;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@ -43,9 +53,12 @@ public class LdapAuthenticatorTest {
|
||||
@Mock
|
||||
UserAccountDao userAccountDao;
|
||||
@Mock
|
||||
AccountManager accountManager;
|
||||
@Mock
|
||||
UserAccount user = new UserAccountVO();
|
||||
|
||||
LdapAuthenticator ldapAuthenticator;
|
||||
@InjectMocks
|
||||
LdapAuthenticator ldapAuthenticator = new LdapAuthenticator();
|
||||
private String username = "bbanner";
|
||||
private String principal = "cd=bbanner";
|
||||
private String hardcoded = "password";
|
||||
@ -53,7 +66,18 @@ public class LdapAuthenticatorTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ldapAuthenticator = new LdapAuthenticator(ldapManager, userAccountDao);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateAsNativeUser() throws Exception {
|
||||
final UserAccountVO user = new UserAccountVO();
|
||||
user.setSource(User.Source.NATIVE);
|
||||
|
||||
when(userAccountDao.getUserAccount(username, domainId)).thenReturn(user);
|
||||
Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> rc;
|
||||
rc = ldapAuthenticator.authenticate(username, "password", domainId, (Map<String, Object[]>)null);
|
||||
assertFalse("authentication succeeded when it should have failed", rc.first());
|
||||
assertEquals("We should not have tried to authenticate", null,rc.second());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -62,9 +86,39 @@ public class LdapAuthenticatorTest {
|
||||
Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> rc;
|
||||
when(ldapManager.getUser(username, domainId)).thenReturn(ldapUser);
|
||||
rc = ldapAuthenticator.authenticate(username, "password", domainId, user);
|
||||
assertFalse("authentication succeded when it should have failed", rc.first());
|
||||
assertFalse("authentication succeeded when it should have failed", rc.first());
|
||||
assertEquals("", UserAuthenticator.ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT,rc.second());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateFailingOnSyncedAccount() throws Exception {
|
||||
Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> rc;
|
||||
|
||||
List<String> memberships = new ArrayList<>();
|
||||
memberships.add("g1");
|
||||
List<String> mappedGroups = new ArrayList<>();
|
||||
mappedGroups.add("g1");
|
||||
mappedGroups.add("g2");
|
||||
|
||||
LdapUser ldapUser = new LdapUser(username,"a@b","b","banner",principal,"",false,null);
|
||||
LdapUser userSpy = spy(ldapUser);
|
||||
when(userSpy.getMemberships()).thenReturn(memberships);
|
||||
|
||||
List<LdapTrustMapVO> maps = new ArrayList<>();
|
||||
LdapAuthenticator auth = spy(ldapAuthenticator);
|
||||
when(auth.getMappedGroups(maps)).thenReturn(mappedGroups);
|
||||
|
||||
LdapTrustMapVO trustMap = new LdapTrustMapVO(domainId, LdapManager.LinkType.GROUP, "cn=name", (short)2, 1l);
|
||||
|
||||
AccountVO account = new AccountVO("accountName" , domainId, "domain.net", (short)2, "final String uuid");
|
||||
when(accountManager.getAccount(anyLong())).thenReturn(account);
|
||||
when(ldapManager.getUser(username, domainId)).thenReturn(userSpy);
|
||||
when(ldapManager.getLinkedLdapGroup(domainId, "g1")).thenReturn(trustMap);
|
||||
rc = auth.authenticate(username, "password", domainId, user, maps);
|
||||
assertFalse("authentication succeeded when it should have failed", rc.first());
|
||||
assertEquals("", UserAuthenticator.ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT,rc.second());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate() throws Exception {
|
||||
LdapUser ldapUser = new LdapUser(username, "a@b", "b", "banner", principal, "", false, null);
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
// under the License.
|
||||
package org.apache.cloudstack.ldap;
|
||||
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.ldap.dao.LdapConfigurationDao;
|
||||
import org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl;
|
||||
import org.junit.Before;
|
||||
@ -24,118 +23,98 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LdapConfigurationTest {
|
||||
|
||||
private final LdapTestConfigTool ldapTestConfigTool = new LdapTestConfigTool();
|
||||
LdapConfigurationDao ldapConfigurationDao;
|
||||
LdapConfiguration ldapConfiguration;
|
||||
|
||||
private void overrideConfigValue(final String configKeyName, final Object o) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field configKey = LdapConfiguration.class.getDeclaredField(configKeyName);
|
||||
configKey.setAccessible(true);
|
||||
|
||||
ConfigKey key = (ConfigKey)configKey.get(ldapConfiguration);
|
||||
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(configKey, configKey.getModifiers() & ~Modifier.FINAL);
|
||||
|
||||
Field f = ConfigKey.class.getDeclaredField("_value");
|
||||
f.setAccessible(true);
|
||||
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
|
||||
f.set(key, o);
|
||||
|
||||
Field dynamic = ConfigKey.class.getDeclaredField("_isDynamic");
|
||||
dynamic.setAccessible(true);
|
||||
modifiersField.setInt(dynamic, dynamic.getModifiers() & ~Modifier.FINAL);
|
||||
dynamic.setBoolean(key, false);
|
||||
private void overrideConfigValue(LdapConfiguration ldapConfiguration, final String configKeyName, final Object o) throws IllegalAccessException, NoSuchFieldException
|
||||
{
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, configKeyName, o);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
ldapConfigurationDao = new LdapConfigurationDaoImpl();
|
||||
ldapConfiguration = new LdapConfiguration(ldapConfigurationDao);;
|
||||
@Before public void init() throws Exception {
|
||||
ldapConfigurationDao = new LdapConfigurationDaoImpl();
|
||||
ldapConfiguration = new LdapConfiguration(ldapConfigurationDao);
|
||||
;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAuthenticationReturnsSimple() throws Exception {
|
||||
overrideConfigValue("ldapBindPrincipal", "cn=bla");
|
||||
overrideConfigValue("ldapBindPassword", "pw");
|
||||
@Test public void getAuthenticationReturnsSimple() throws Exception {
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapBindPrincipal", "cn=bla");
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapBindPassword", "pw");
|
||||
String authentication = ldapConfiguration.getAuthentication(null);
|
||||
assertEquals("authentication should be set to simple", "simple", authentication);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getBaseDnReturnsABaseDn() throws Exception {
|
||||
overrideConfigValue("ldapBaseDn", "dc=cloudstack,dc=org");
|
||||
@Test public void getBaseDnReturnsABaseDn() throws Exception {
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapBaseDn", "dc=cloudstack,dc=org");
|
||||
String baseDn = ldapConfiguration.getBaseDn(null);
|
||||
assertEquals("The set baseDn should be returned","dc=cloudstack,dc=org", baseDn);
|
||||
assertEquals("The set baseDn should be returned", "dc=cloudstack,dc=org", baseDn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGroupUniqueMemberAttribute() throws Exception {
|
||||
String [] groupNames = {"bla", "uniquemember", "memberuid", "", null};
|
||||
for (String groupObject: groupNames) {
|
||||
overrideConfigValue("ldapGroupUniqueMemberAttribute", groupObject);
|
||||
@Test public void getGroupUniqueMemberAttribute() throws Exception {
|
||||
String[] groupNames = {"bla", "uniquemember", "memberuid", "", null};
|
||||
for (String groupObject : groupNames) {
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapGroupUniqueMemberAttribute", groupObject);
|
||||
String expectedResult = null;
|
||||
if(groupObject == null) {
|
||||
if (groupObject == null) {
|
||||
expectedResult = "uniquemember";
|
||||
} else {
|
||||
expectedResult = groupObject;
|
||||
};
|
||||
}
|
||||
;
|
||||
String result = ldapConfiguration.getGroupUniqueMemberAttribute(null);
|
||||
assertEquals("testing for " + groupObject, expectedResult, result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSSLStatusCanBeTrue() throws Exception {
|
||||
@Test public void getSSLStatusCanBeTrue() throws Exception {
|
||||
// given: "We have a ConfigDao with values for truststore and truststore password set"
|
||||
overrideConfigValue("ldapTrustStore", "/tmp/ldap.ts");
|
||||
overrideConfigValue("ldapTrustStorePassword", "password");
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapTrustStore", "/tmp/ldap.ts");
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapTrustStorePassword", "password");
|
||||
|
||||
assertTrue("A request is made to get the status of SSL should result in true", ldapConfiguration.getSSLStatus());
|
||||
}
|
||||
@Test
|
||||
public void getSearchGroupPrincipleReturnsSuccessfully() throws Exception {
|
||||
|
||||
@Test public void getSearchGroupPrincipleReturnsSuccessfully() throws Exception {
|
||||
// We have a ConfigDao with a value for ldap.search.group.principle and an LdapConfiguration
|
||||
overrideConfigValue("ldapSearchGroupPrinciple", "cn=cloudstack,cn=users,dc=cloudstack,dc=org");
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapSearchGroupPrinciple", "cn=cloudstack,cn=users,dc=cloudstack,dc=org");
|
||||
String result = ldapConfiguration.getSearchGroupPrinciple(null);
|
||||
|
||||
assertEquals("The result holds the same value configDao did", "cn=cloudstack,cn=users,dc=cloudstack,dc=org",result);
|
||||
assertEquals("The result holds the same value configDao did", "cn=cloudstack,cn=users,dc=cloudstack,dc=org", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrustStorePasswordResopnds() throws Exception {
|
||||
@Test public void getTrustStorePasswordResopnds() throws Exception {
|
||||
// We have a ConfigDao with a value for truststore password
|
||||
overrideConfigValue("ldapTrustStorePassword", "password");
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapTrustStorePassword", "password");
|
||||
|
||||
String result = ldapConfiguration.getTrustStorePassword();
|
||||
|
||||
assertEquals("The result is password", "password", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getGroupObject() throws Exception {
|
||||
String [] groupNames = {"bla", "groupOfUniqueNames", "groupOfNames", "", null};
|
||||
for (String groupObject: groupNames) {
|
||||
overrideConfigValue("ldapGroupObject", groupObject);
|
||||
@Test public void getGroupObject() throws Exception {
|
||||
String[] groupNames = {"bla", "groupOfUniqueNames", "groupOfNames", "", null};
|
||||
for (String groupObject : groupNames) {
|
||||
ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapGroupObject", groupObject);
|
||||
String expectedResult = null;
|
||||
if(groupObject == null) {
|
||||
if (groupObject == null) {
|
||||
expectedResult = "groupOfUniqueNames";
|
||||
} else {
|
||||
expectedResult = groupObject;
|
||||
};
|
||||
}
|
||||
;
|
||||
String result = ldapConfiguration.getGroupObject(null);
|
||||
assertEquals("testing for " + groupObject, expectedResult, result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void getNullLdapProvider() {
|
||||
assertEquals(LdapUserManager.Provider.OPENLDAP, ldapConfiguration.getLdapProvider(null));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you 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 org.apache.cloudstack.ldap;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
import org.apache.cloudstack.ldap.dao.LdapConfigurationDao;
|
||||
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
|
||||
import org.apache.directory.api.ldap.model.entry.Entry;
|
||||
import org.apache.directory.api.ldap.model.exception.LdapException;
|
||||
import org.apache.directory.api.ldap.model.message.AddRequest;
|
||||
import org.apache.directory.api.ldap.model.message.AddRequestImpl;
|
||||
import org.apache.directory.api.ldap.model.message.AddResponse;
|
||||
import org.apache.directory.ldap.client.api.LdapConnection;
|
||||
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
|
||||
import org.apache.directory.server.core.api.DirectoryService;
|
||||
import org.apache.directory.server.core.api.changelog.ChangeLog;
|
||||
import org.apache.directory.server.ldap.LdapServer;
|
||||
import org.apache.directory.server.xdbm.IndexNotFoundException;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LdapDirectoryServerConnectionTest {
|
||||
|
||||
static EmbeddedLdapServer embeddedLdapServer;
|
||||
|
||||
@Mock
|
||||
LdapConfigurationDao configurationDao;
|
||||
|
||||
LdapContextFactory contextFactory;
|
||||
|
||||
@Mock
|
||||
LdapUserManagerFactory userManagerFactory;
|
||||
|
||||
@InjectMocks
|
||||
LdapConfiguration configuration;
|
||||
|
||||
@InjectMocks
|
||||
private LdapManagerImpl ldapManager;
|
||||
|
||||
private final LdapTestConfigTool ldapTestConfigTool = new LdapTestConfigTool();
|
||||
|
||||
@BeforeClass
|
||||
public static void start() throws Exception {
|
||||
embeddedLdapServer = new EmbeddedLdapServer();
|
||||
embeddedLdapServer.init();
|
||||
}
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
LdapConfigurationVO configurationVO = new LdapConfigurationVO("localhost",10389,null);
|
||||
when(configurationDao.find("localhost",10389,null)).thenReturn(configurationVO);
|
||||
ldapTestConfigTool.overrideConfigValue(configuration, "ldapBaseDn", "ou=system");
|
||||
ldapTestConfigTool.overrideConfigValue(configuration, "ldapBindPassword", "secret");
|
||||
ldapTestConfigTool.overrideConfigValue(configuration, "ldapBindPrincipal", "uid=admin,ou=system");
|
||||
ldapTestConfigTool.overrideConfigValue(configuration, "ldapMemberOfAttribute", "memberOf");
|
||||
when(userManagerFactory.getInstance(LdapUserManager.Provider.OPENLDAP)).thenReturn(new OpenLdapUserManagerImpl(configuration));
|
||||
// construct an ellaborate structure around a single object
|
||||
Pair<List<LdapConfigurationVO>, Integer> vos = new Pair<List<LdapConfigurationVO>, Integer>( Collections.singletonList(configurationVO),1);
|
||||
when(configurationDao.searchConfigurations(null, 0, 1L)).thenReturn(vos);
|
||||
|
||||
contextFactory = new LdapContextFactory(configuration);
|
||||
ldapManager = new LdapManagerImpl(configurationDao, contextFactory, userManagerFactory, configuration);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() throws Exception {
|
||||
contextFactory = null;
|
||||
ldapManager = null;
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void stop() throws Exception {
|
||||
embeddedLdapServer.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmbeddedLdapServerInitialization() throws IndexNotFoundException {
|
||||
LdapServer ldapServer = embeddedLdapServer.getLdapServer();
|
||||
assertNotNull(ldapServer);
|
||||
|
||||
DirectoryService directoryService = embeddedLdapServer.getDirectoryService();
|
||||
assertNotNull(directoryService);
|
||||
assertNotNull(directoryService.getSchemaPartition());
|
||||
assertNotNull(directoryService.getSystemPartition());
|
||||
assertNotNull(directoryService.getSchemaManager());
|
||||
assertNotNull(directoryService.getDnFactory());
|
||||
|
||||
assertNotNull(directoryService.isDenormalizeOpAttrsEnabled());
|
||||
|
||||
ChangeLog changeLog = directoryService.getChangeLog();
|
||||
|
||||
assertNotNull(changeLog);
|
||||
assertFalse(changeLog.isEnabled());
|
||||
|
||||
assertNotNull(directoryService.isStarted());
|
||||
assertNotNull(ldapServer.isStarted());
|
||||
|
||||
List userList = new ArrayList(embeddedLdapServer.getUserIndexMap().keySet());
|
||||
java.util.Collections.sort(userList);
|
||||
List checkList = Arrays.asList("uid");
|
||||
assertEquals(userList, checkList);
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void testEmbeddedLdapAvailable() {
|
||||
try {
|
||||
List<LdapUser> usahs = ldapManager.getUsers(1L);
|
||||
assertFalse("should find at least the admin user", usahs.isEmpty());
|
||||
} catch (NoLdapUserMatchingQueryException e) {
|
||||
fail(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSchemaLoading() {
|
||||
try {
|
||||
assertTrue("standard not loaded", embeddedLdapServer.addSchemaFromClasspath("other"));
|
||||
// we need member of in ACS nowadays (backwards comptability broken):
|
||||
// assertTrue("memberOf schema not loaded", embeddedLdapServer.addSchemaFromPath(new File("src/test/resources/memberOf"), "microsoft"));
|
||||
} catch (LdapException | IOException e) {
|
||||
fail(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void testUserCreation() {
|
||||
LdapConnection connection = new LdapNetworkConnection( "localhost", 10389 );
|
||||
try {
|
||||
connection.bind( "uid=admin,ou=system", "secret" );
|
||||
|
||||
connection.add(new DefaultEntry(
|
||||
"ou=acsadmins,ou=users,ou=system",
|
||||
"objectClass: organizationalUnit",
|
||||
// might also need to be objectClass: top
|
||||
"ou: acsadmins"
|
||||
));
|
||||
connection.add(new DefaultEntry(
|
||||
"uid=dahn,ou=acsadmins,ou=users,ou=system",
|
||||
"objectClass: inetOrgPerson",
|
||||
"objectClass: top",
|
||||
"cn: dahn",
|
||||
"sn: Hoogland",
|
||||
"givenName: Daan",
|
||||
"mail: d@b.c",
|
||||
"uid: dahn"
|
||||
));
|
||||
|
||||
connection.add(
|
||||
new DefaultEntry(
|
||||
"cn=JuniorAdmins,ou=groups,ou=system", // The Dn
|
||||
"objectClass: groupOfUniqueNames",
|
||||
"ObjectClass: top",
|
||||
"cn: JuniorAdmins",
|
||||
"uniqueMember: uid=dahn,ou=acsadmins,ou=system,ou=users") );
|
||||
|
||||
assertTrue( connection.exists( "cn=JuniorAdmins,ou=groups,ou=system" ) );
|
||||
assertTrue( connection.exists( "uid=dahn,ou=acsadmins,ou=users,ou=system" ) );
|
||||
|
||||
Entry ourUser = connection.lookup("uid=dahn,ou=acsadmins,ou=users,ou=system");
|
||||
ourUser.add("memberOf", "cn=JuniorAdmins,ou=groups,ou=system");
|
||||
AddRequest addRequest = new AddRequestImpl();
|
||||
addRequest.setEntry( ourUser );
|
||||
AddResponse response = connection.add( addRequest );
|
||||
assertNotNull( response );
|
||||
// We would need to either
|
||||
// assertEquals( ResultCodeEnum.SUCCESS, response.getLdapResult().getResultCode() );
|
||||
// or have the automatic virtual attribute
|
||||
|
||||
List<LdapUser> usahs = ldapManager.getUsers(1L);
|
||||
assertEquals("now an admin and a normal user should be present",2, usahs.size());
|
||||
|
||||
} catch (LdapException | NoLdapUserMatchingQueryException e) {
|
||||
fail(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you 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 org.apache.cloudstack.ldap;
|
||||
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
public class LdapTestConfigTool {
|
||||
public LdapTestConfigTool() {
|
||||
}
|
||||
|
||||
void overrideConfigValue(LdapConfiguration ldapConfiguration, final String configKeyName, final Object o) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field configKey = LdapConfiguration.class.getDeclaredField(configKeyName);
|
||||
configKey.setAccessible(true);
|
||||
|
||||
ConfigKey key = (ConfigKey)configKey.get(ldapConfiguration);
|
||||
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(configKey, configKey.getModifiers() & ~Modifier.FINAL);
|
||||
|
||||
Field f = ConfigKey.class.getDeclaredField("_value");
|
||||
f.setAccessible(true);
|
||||
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
|
||||
f.set(key, o);
|
||||
|
||||
Field dynamic = ConfigKey.class.getDeclaredField("_isDynamic");
|
||||
dynamic.setAccessible(true);
|
||||
modifiersField.setInt(dynamic, dynamic.getModifiers() & ~Modifier.FINAL);
|
||||
dynamic.setBoolean(key, false);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.cloudstack.ldap;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.unboundid.ldap.sdk.LDAPConnection;
|
||||
import com.unboundid.ldap.sdk.LDAPInterface;
|
||||
import com.unboundid.ldap.sdk.SearchResult;
|
||||
import com.unboundid.ldap.sdk.SearchScope;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.zapodot.junit.ldap.EmbeddedLdapRule;
|
||||
import org.zapodot.junit.ldap.EmbeddedLdapRuleBuilder;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LdapUnboundidZapdotConnectionTest {
|
||||
private static final String DOMAIN_DSN;
|
||||
|
||||
static {
|
||||
DOMAIN_DSN = "dc=cloudstack,dc=org";
|
||||
}
|
||||
|
||||
@Rule
|
||||
public EmbeddedLdapRule embeddedLdapRule = EmbeddedLdapRuleBuilder
|
||||
.newInstance()
|
||||
.usingDomainDsn(DOMAIN_DSN)
|
||||
.importingLdifs("unboundid.ldif")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testLdapInteface() throws Exception {
|
||||
// Test using the UnboundID LDAP SDK directly
|
||||
final LDAPInterface ldapConnection = embeddedLdapRule.ldapConnection();
|
||||
final SearchResult searchResult = ldapConnection.search(DOMAIN_DSN, SearchScope.SUB, "(objectClass=person)");
|
||||
assertEquals(24, searchResult.getEntryCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsharedLdapConnection() throws Exception {
|
||||
// Test using the UnboundID LDAP SDK directly by using the UnboundID LDAPConnection type
|
||||
final LDAPConnection ldapConnection = embeddedLdapRule.unsharedLdapConnection();
|
||||
final SearchResult searchResult = ldapConnection.search(DOMAIN_DSN, SearchScope.SUB, "(objectClass=person)");
|
||||
assertEquals(24, searchResult.getEntryCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirContext() throws Exception {
|
||||
|
||||
// Test using the good ol' JDNI-LDAP integration
|
||||
final DirContext dirContext = embeddedLdapRule.dirContext();
|
||||
final SearchControls searchControls = new SearchControls();
|
||||
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
final NamingEnumeration<javax.naming.directory.SearchResult> resultNamingEnumeration =
|
||||
dirContext.search(DOMAIN_DSN, "(objectClass=person)", searchControls);
|
||||
assertEquals(24, Iterators.size(Iterators.forEnumeration(resultNamingEnumeration)));
|
||||
}
|
||||
@Test
|
||||
public void testContext() throws Exception {
|
||||
|
||||
// Another test using the good ol' JDNI-LDAP integration, this time with the Context interface
|
||||
final Context context = embeddedLdapRule.context();
|
||||
final Object user = context.lookup("cn=Cammy Petri,dc=cloudstack,dc=org");
|
||||
assertNotNull(user);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.cloudstack.ldap;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.btmatthews.ldapunit.DirectoryTester;
|
||||
import com.btmatthews.ldapunit.DirectoryServerConfiguration;
|
||||
import com.btmatthews.ldapunit.DirectoryServerRule;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@DirectoryServerConfiguration(ldifFiles = {LdapUnitConnectionTest.LDIF_FILE_NAME},
|
||||
baseDN = LdapUnitConnectionTest.DOMAIN_DSN,
|
||||
port = LdapUnitConnectionTest.PORT,
|
||||
authDN = LdapUnitConnectionTest.BIND_DN,
|
||||
authPassword = LdapUnitConnectionTest.SECRET)
|
||||
public class LdapUnitConnectionTest {
|
||||
static final String LDIF_FILE_NAME = "ldapunit.ldif";
|
||||
static final String DOMAIN_DSN = "dc=am,dc=echt,dc=net";
|
||||
static final String BIND_DN = "uid=admin,ou=cloudstack";
|
||||
static final String SECRET = "secretzz";
|
||||
static final int PORT =11389;
|
||||
|
||||
@Rule
|
||||
public DirectoryServerRule directoryServerRule = new DirectoryServerRule();
|
||||
|
||||
private DirectoryTester directoryTester;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
directoryTester = new DirectoryTester("localhost", PORT, BIND_DN, SECRET);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
directoryTester.disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapInteface() throws Exception {
|
||||
directoryTester.assertDNExists("dc=am,dc=echt,dc=net");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you 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 org.apache.cloudstack.ldap;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LdapUserManagerFactoryTest {
|
||||
|
||||
@Mock
|
||||
ApplicationContext applicationCtx;
|
||||
|
||||
@Mock
|
||||
AutowireCapableBeanFactory autowireCapableBeanFactory;
|
||||
|
||||
@Mock
|
||||
protected LdapConfiguration _ldapConfiguration;
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
static LdapUserManagerFactory ldapUserManagerFactory = new LdapUserManagerFactory();
|
||||
|
||||
/**
|
||||
* circumvent springframework for these {code ManagerImpl}
|
||||
*/
|
||||
@BeforeClass
|
||||
public static void init()
|
||||
{
|
||||
ldapUserManagerFactory.ldapUserManagerMap.put(LdapUserManager.Provider.MICROSOFTAD, new ADLdapUserManagerImpl());
|
||||
ldapUserManagerFactory.ldapUserManagerMap.put(LdapUserManager.Provider.OPENLDAP, new OpenLdapUserManagerImpl());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
}
|
||||
@Test
|
||||
public void getOpenLdapInstance() {
|
||||
LdapUserManager userManager = ldapUserManagerFactory.getInstance(LdapUserManager.Provider.OPENLDAP);
|
||||
assertTrue("x dude", userManager instanceof OpenLdapUserManagerImpl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMSADInstance() {
|
||||
LdapUserManager userManager = ldapUserManagerFactory.getInstance(LdapUserManager.Provider.MICROSOFTAD);
|
||||
assertTrue("wrong dude", userManager instanceof ADLdapUserManagerImpl);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
version: 1
|
||||
|
||||
dn: ou=groups,dc=am,dc=echt,dc=net
|
||||
objectClass: organizationalUnit
|
||||
objectClass: top
|
||||
ou: Groups
|
||||
|
||||
dn: cn=JuniorAdmins,ou=groups,dc=am,dc=echt,dc=net
|
||||
objectClass: groupOfUniqueNames
|
||||
objectClass: top
|
||||
cn: JuniorAdmins
|
||||
uniqueMember: uid=demo,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=demo2,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=double,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
|
||||
dn: ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: organizationalUnit
|
||||
objectClass: top
|
||||
ou: acsadmins
|
||||
|
||||
dn: uid=dahn,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
cn: dahn
|
||||
sn: Hoogland
|
||||
givenName: Daan
|
||||
mail: d@b.c
|
||||
uid: dahn
|
||||
|
||||
dn: uid=demo,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
mail: d@b.c
|
||||
uid: demo
|
||||
|
||||
dn: cn=SeniorAdmins,ou=groups,dc=am,dc=echt,dc=net
|
||||
objectClass: groupOfUniqueNames
|
||||
objectClass: top
|
||||
cn: SeniorAdmins
|
||||
uniqueMember: uid=pga,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=demo4,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
|
||||
dn: cn=admins,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: groupOfNames
|
||||
objectClass: top
|
||||
cn: admins
|
||||
member: uid=dahn,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=demo,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=demo2,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=demo3,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=demo4,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=pga,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=double,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
|
||||
dn: uid=pga,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalperson
|
||||
objectClass: top
|
||||
objectClass: inetorgperson
|
||||
cn: Paul Angus
|
||||
sn: angus
|
||||
givenName: paul
|
||||
mail: paul.angus@shapeblue.com
|
||||
uid: pga
|
||||
|
||||
dn: uid=demo2,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
mail: d@b.c
|
||||
uid: demo2
|
||||
|
||||
dn: uid=demo3,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
mail: d@b.c
|
||||
uid: demo3
|
||||
|
||||
dn: uid=demo4,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
mail: d@b.c
|
||||
uid: demo4
|
||||
|
||||
dn: cn=Admins,ou=groups,dc=am,dc=echt,dc=net
|
||||
objectClass: groupOfUniqueNames
|
||||
objectClass: top
|
||||
cn: Admins
|
||||
uniqueMember: uid=dahn,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=demo3,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=double,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=noadmin,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
|
||||
dn: uid=double,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
mail: d@b.c
|
||||
uid: double
|
||||
|
||||
dn: uid=noadmin,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
mail: d@b.c
|
||||
uid: noadmin
|
||||
78
plugins/user-authenticators/ldap/src/test/resources/log4j.xml
Executable file
78
plugins/user-authenticators/ldap/src/test/resources/log4j.xml
Executable file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
|
||||
-->
|
||||
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
|
||||
|
||||
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
|
||||
|
||||
<throwableRenderer class="com.cloud.utils.log.CglibThrowableRenderer"/>
|
||||
<!-- ================================= -->
|
||||
<!-- Preserve messages in a local file -->
|
||||
<!-- ================================= -->
|
||||
|
||||
<!-- ============================== -->
|
||||
<!-- Append messages to the console -->
|
||||
<!-- ============================== -->
|
||||
|
||||
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
|
||||
<param name="Target" value="System.out"/>
|
||||
<param name="Threshold" value="TRACE"/>
|
||||
<layout class="org.apache.log4j.PatternLayout">
|
||||
<param name="ConversionPattern" value="%d{ISO8601} %-5p [%c{3}] (%t:%x) %m%n"/>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<!-- ================ -->
|
||||
<!-- Limit categories -->
|
||||
<!-- ================ -->
|
||||
|
||||
<category name="com.cloud">
|
||||
<priority value="DEBUG"/>
|
||||
</category>
|
||||
|
||||
<!-- Limit the org.apache category to INFO as its DEBUG is verbose -->
|
||||
<category name="org.apache.cloudstack">
|
||||
<priority value="DEBUG"/>
|
||||
</category>
|
||||
|
||||
<category name="org.apache.directory">
|
||||
<priority value="WARN"/>
|
||||
</category>
|
||||
|
||||
<category name="org.apache.directory.api.ldap.model.entry.Value">
|
||||
<priority value="FATAL"/>
|
||||
</category>
|
||||
|
||||
<category name="org.apache.directory.api.ldap.model.entry.DefaultAttribute">
|
||||
<priority value="FATAL"/>
|
||||
</category>
|
||||
|
||||
|
||||
<!-- ======================= -->
|
||||
<!-- Setup the Root category -->
|
||||
<!-- ======================= -->
|
||||
|
||||
<root>
|
||||
<level value="INFO"/>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
</log4j:configuration>
|
||||
243
plugins/user-authenticators/ldap/src/test/resources/minimal.ldif
Normal file
243
plugins/user-authenticators/ldap/src/test/resources/minimal.ldif
Normal file
@ -0,0 +1,243 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
version: 1
|
||||
|
||||
dn: dc=am,dc=echt,dc=net
|
||||
objectClass: domain
|
||||
objectClass: top
|
||||
dc: am
|
||||
|
||||
dn: ou=groups,dc=am,dc=echt,dc=net
|
||||
objectClass: organizationalUnit
|
||||
objectClass: top
|
||||
ou: Groups
|
||||
|
||||
dn: cn=JuniorAdmins,ou=groups,dc=am,dc=echt,dc=net
|
||||
objectClass: groupOfUniqueNames
|
||||
objectClass: top
|
||||
cn: JuniorAdmins
|
||||
uniqueMember: uid=demo,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=demo2,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=double,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
|
||||
dn: ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: organizationalUnit
|
||||
objectClass: top
|
||||
ou: acsadmins
|
||||
|
||||
dn: uid=dahn,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: sunFederationManagerDataStore
|
||||
cn: dahn
|
||||
sn: Hoogland
|
||||
givenName: Daan
|
||||
inetUserStatus: Active
|
||||
mail: d@b.c
|
||||
uid: dahn
|
||||
|
||||
dn: uid=demo,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: iplanet-am-user-service
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: sunAMAuthAccountLockout
|
||||
objectClass: iPlanetPreferences
|
||||
objectClass: top
|
||||
objectClass: sunIdentityServerLibertyPPService
|
||||
objectClass: sunFMSAML2NameIdentifier
|
||||
objectClass: forgerock-am-dashboard-service
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: sunFederationManagerDataStore
|
||||
objectClass: devicePrintProfilesContainer
|
||||
objectClass: iplanet-am-auth-configuration-service
|
||||
objectClass: iplanet-am-managed-person
|
||||
objectClass: inetuser
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
inetUserStatus: Active
|
||||
mail: d@b.c
|
||||
uid: demo
|
||||
|
||||
dn: cn=SeniorAdmins,ou=groups,dc=am,dc=echt,dc=net
|
||||
objectClass: groupOfUniqueNames
|
||||
objectClass: top
|
||||
cn: SeniorAdmins
|
||||
uniqueMember: uid=pga,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=demo4,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
|
||||
dn: cn=admins,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: groupOfNames
|
||||
objectClass: top
|
||||
cn: admins
|
||||
member: uid=dahn,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=demo,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=demo2,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=demo3,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=demo4,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=pga,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
member: uid=double,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
|
||||
dn: uid=pga,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: iplanet-am-user-service
|
||||
objectClass: person
|
||||
objectClass: organizationalperson
|
||||
objectClass: sunAMAuthAccountLockout
|
||||
objectClass: iPlanetPreferences
|
||||
objectClass: top
|
||||
objectClass: sunIdentityServerLibertyPPService
|
||||
objectClass: sunFMSAML2NameIdentifier
|
||||
objectClass: forgerock-am-dashboard-service
|
||||
objectClass: inetorgperson
|
||||
objectClass: sunFederationManagerDataStore
|
||||
objectClass: devicePrintProfilesContainer
|
||||
objectClass: iplanet-am-auth-configuration-service
|
||||
objectClass: iplanet-am-managed-person
|
||||
objectClass: inetuser
|
||||
cn: Paul Angus
|
||||
sn: angus
|
||||
givenName: paul
|
||||
inetUserStatus: Active
|
||||
mail: paul.angus@shapeblue.com
|
||||
uid: pga
|
||||
|
||||
dn: uid=demo2,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: iplanet-am-user-service
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: sunAMAuthAccountLockout
|
||||
objectClass: iPlanetPreferences
|
||||
objectClass: top
|
||||
objectClass: sunIdentityServerLibertyPPService
|
||||
objectClass: sunFMSAML2NameIdentifier
|
||||
objectClass: forgerock-am-dashboard-service
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: sunFederationManagerDataStore
|
||||
objectClass: devicePrintProfilesContainer
|
||||
objectClass: iplanet-am-auth-configuration-service
|
||||
objectClass: iplanet-am-managed-person
|
||||
objectClass: inetuser
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
inetUserStatus: Active
|
||||
mail: d@b.c
|
||||
uid: demo2
|
||||
|
||||
dn: uid=demo3,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: iplanet-am-user-service
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: sunAMAuthAccountLockout
|
||||
objectClass: iPlanetPreferences
|
||||
objectClass: top
|
||||
objectClass: sunIdentityServerLibertyPPService
|
||||
objectClass: sunFMSAML2NameIdentifier
|
||||
objectClass: forgerock-am-dashboard-service
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: sunFederationManagerDataStore
|
||||
objectClass: devicePrintProfilesContainer
|
||||
objectClass: iplanet-am-auth-configuration-service
|
||||
objectClass: iplanet-am-managed-person
|
||||
objectClass: inetuser
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
inetUserStatus: Active
|
||||
mail: d@b.c
|
||||
uid: demo3
|
||||
|
||||
dn: uid=demo4,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: iplanet-am-user-service
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: sunAMAuthAccountLockout
|
||||
objectClass: iPlanetPreferences
|
||||
objectClass: top
|
||||
objectClass: sunIdentityServerLibertyPPService
|
||||
objectClass: sunFMSAML2NameIdentifier
|
||||
objectClass: forgerock-am-dashboard-service
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: sunFederationManagerDataStore
|
||||
objectClass: devicePrintProfilesContainer
|
||||
objectClass: iplanet-am-auth-configuration-service
|
||||
objectClass: iplanet-am-managed-person
|
||||
objectClass: inetuser
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
inetUserStatus: Active
|
||||
mail: d@b.c
|
||||
uid: demo4
|
||||
|
||||
dn: cn=Admins,ou=groups,dc=am,dc=echt,dc=net
|
||||
objectClass: groupOfUniqueNames
|
||||
objectClass: top
|
||||
cn: Admins
|
||||
uniqueMember: uid=dahn,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=demo3,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=double,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
uniqueMember: uid=noadmin,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
|
||||
dn: uid=double,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: iplanet-am-user-service
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: sunAMAuthAccountLockout
|
||||
objectClass: iPlanetPreferences
|
||||
objectClass: top
|
||||
objectClass: sunIdentityServerLibertyPPService
|
||||
objectClass: sunFMSAML2NameIdentifier
|
||||
objectClass: forgerock-am-dashboard-service
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: sunFederationManagerDataStore
|
||||
objectClass: devicePrintProfilesContainer
|
||||
objectClass: iplanet-am-auth-configuration-service
|
||||
objectClass: iplanet-am-managed-person
|
||||
objectClass: inetuser
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
inetUserStatus: Active
|
||||
mail: d@b.c
|
||||
uid: double
|
||||
|
||||
dn: uid=noadmin,ou=acsadmins,dc=am,dc=echt,dc=net
|
||||
objectClass: iplanet-am-user-service
|
||||
objectClass: person
|
||||
objectClass: organizationalPerson
|
||||
objectClass: sunAMAuthAccountLockout
|
||||
objectClass: iPlanetPreferences
|
||||
objectClass: top
|
||||
objectClass: sunIdentityServerLibertyPPService
|
||||
objectClass: sunFMSAML2NameIdentifier
|
||||
objectClass: forgerock-am-dashboard-service
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: sunFederationManagerDataStore
|
||||
objectClass: devicePrintProfilesContainer
|
||||
objectClass: iplanet-am-auth-configuration-service
|
||||
objectClass: iplanet-am-managed-person
|
||||
objectClass: inetuser
|
||||
cn: demo
|
||||
sn: User
|
||||
givenName: demo
|
||||
inetUserStatus: Active
|
||||
mail: d@b.c
|
||||
uid: noadmin
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<bean id="LdapAuthenticator" class="org.apache.cloudstack.ldap.LdapAuthenticator">
|
||||
<property name="name" value="LDAP" />
|
||||
</bean>
|
||||
|
||||
<bean id="LdapManager" class="org.apache.cloudstack.ldap.LdapManagerImpl" />
|
||||
<bean id="LdapUserManagerFactory" class="org.apache.cloudstack.ldap.LdapUserManagerFactory" />
|
||||
<bean id="LdapContextFactory" class="org.apache.cloudstack.ldap.LdapContextFactory" />
|
||||
<bean id="LdapConfigurationDao"
|
||||
class="org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl" />
|
||||
<bean id="LdapConfiguration" class="org.apache.cloudstack.ldap.LdapConfiguration" />
|
||||
<bean id="LdapTrustMapDao" class="org.apache.cloudstack.ldap.dao.LdapTrustMapDaoImpl" />
|
||||
|
||||
</beans>
|
||||
@ -0,0 +1,311 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
version: 1
|
||||
|
||||
dn: dc=cloudstack,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
dc: cloudstack
|
||||
o: cloudstack
|
||||
|
||||
dn: cn=Ryan Murphy,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Ryan Murphy
|
||||
sn: Murphy
|
||||
givenName: Ryan
|
||||
mail: rmurphy@cloudstack.org
|
||||
uid: rmurphy
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Barbara Brewer,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Barbara Brewer
|
||||
sn: Brewer
|
||||
mail: bbrewer@cloudstack.org
|
||||
uid: bbrewer
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Zak Wilkinson,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Zak Wilkinson
|
||||
givenname: Zak
|
||||
sn: Wilkinson
|
||||
uid: zwilkinson
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Archie Shingleton,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Archie Shingleton
|
||||
sn: Shingleton
|
||||
givenName: Archie
|
||||
mail: ashingleton@cloudstack.org
|
||||
uid: ashingleton
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Cletus Pears,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Cletus Pears
|
||||
sn: Pears
|
||||
givenName: Cletus
|
||||
mail: cpears@cloudstack.org
|
||||
uid: cpears
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Teisha Milewski,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Teisha Milewski
|
||||
sn: Milewski
|
||||
givenName: Teisha
|
||||
mail: tmilewski@cloudstack.org
|
||||
uid: tmilewski
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Eloy Para,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Eloy Para
|
||||
sn: Para
|
||||
givenName: Eloy
|
||||
mail: epara@cloudstack.org
|
||||
uid: epara
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Elaine Lamb,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Elaine Lamb
|
||||
sn: Lamb
|
||||
givenName: Elaine
|
||||
mail: elamb@cloudstack.org
|
||||
uid: elamb
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Soon Griffen,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Soon Griffen
|
||||
sn: Griffen
|
||||
givenName: Soon
|
||||
mail: sgriffen@cloudstack.org
|
||||
uid: sgriffen
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Tran Neisler,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Tran Neisler
|
||||
sn: Neisler
|
||||
givenName: Tran
|
||||
mail: tneisler@cloudstack.org
|
||||
uid: tneisler
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Mirella Zeck,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Mirella Zeck
|
||||
sn: Zeck
|
||||
givenName: Mirella
|
||||
mail: mzeck@cloudstack.org
|
||||
uid: mzeck
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Greg Hoskin,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Greg Hoskin
|
||||
sn: Hoskin
|
||||
givenName: Greg
|
||||
mail: ghoskin@cloudstack.org
|
||||
uid: ghoskin
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Johanne Runyon,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Johanne Runyon
|
||||
sn: Runyon
|
||||
givenName: Johanne
|
||||
mail: jrunyon@cloudstack.org
|
||||
uid: jrunyon
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Mabelle Waiters,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Mabelle Waiters
|
||||
sn: Waiters
|
||||
givenName: Mabelle
|
||||
mail: mwaiters@cloudstack.org
|
||||
uid: mwaiters
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Phillip Fruge,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Phillip Fruge
|
||||
sn: Fruge
|
||||
givenName: Phillip
|
||||
mail: pfruge@cloudstack.org
|
||||
uid: pfruge
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Jayna Ridenhour,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Jayna Ridenhour
|
||||
sn: Ridenhour
|
||||
givenName: Jayna
|
||||
mail: jridenhour@cloudstack.org
|
||||
uid: jridenhour
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Marlyn Mandujano,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Marlyn Mandujano
|
||||
sn: Mandujano
|
||||
givenName: Marlyn
|
||||
mail: mmandujano@cloudstack.org
|
||||
uid: mmandujano
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Shaunna Scherer,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Shaunna Scherer
|
||||
sn: Scherer
|
||||
givenName: Shaunna
|
||||
mail: sscherer@cloudstack.org
|
||||
uid: sscherer
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Adriana Bozek,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Adriana Bozek
|
||||
sn: Bozek
|
||||
givenName: Adriana
|
||||
mail: abozek@cloudstack.org
|
||||
uid: abozek
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Silvana Chipman,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Silvana Chipman
|
||||
sn: Chipman
|
||||
givenName: Silvana
|
||||
mail: schipman@cloudstack.org
|
||||
uid: schipman
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Marion Wasden,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Marion Wasden
|
||||
sn: Wasden
|
||||
givenName: Marion
|
||||
mail: mwasden@cloudstack.org
|
||||
uid: mwasden
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Anisa Casson,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Anisa Casson
|
||||
sn: Casson
|
||||
givenName: Anisa
|
||||
mail: acasson@cloudstack.org
|
||||
uid: acasson
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
dn: cn=Noel King,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Noel King
|
||||
sn: King
|
||||
givenName: Noel
|
||||
mail: nking@cloudstack.org
|
||||
uid: nking
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
|
||||
dn: cn=Cammy Petri,dc=cloudstack,dc=org
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
cn: Cammy Petri
|
||||
sn: Petri
|
||||
givenName: Cammy
|
||||
mail: cpetri@cloudstack.org
|
||||
uid: cpetri
|
||||
userpassword:: cGFzc3dvcmQ=
|
||||
|
||||
@ -413,6 +413,36 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
||||
return response;
|
||||
}
|
||||
|
||||
public ListResponse<UserResponse> searchForUsers(Long domainId, boolean recursive) throws PermissionDeniedException {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
|
||||
List<Long> permittedAccounts = new ArrayList<Long>();
|
||||
|
||||
boolean listAll = true;
|
||||
Long id = null;
|
||||
|
||||
if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL) {
|
||||
long currentId = CallContext.current().getCallingUser().getId();
|
||||
if (id != null && currentId != id.longValue()) {
|
||||
throw new PermissionDeniedException("Calling user is not authorized to see the user requested by id");
|
||||
}
|
||||
id = currentId;
|
||||
}
|
||||
Object username = null;
|
||||
Object type = null;
|
||||
String accountName = null;
|
||||
Object state = null;
|
||||
Object keyword = null;
|
||||
|
||||
Pair<List<UserAccountJoinVO>, Integer> result = getUserListInternal(caller, permittedAccounts, listAll, id, username, type, accountName, state, keyword, domainId, recursive,
|
||||
null);
|
||||
ListResponse<UserResponse> response = new ListResponse<UserResponse>();
|
||||
List<UserResponse> userResponses = ViewResponseHelper.createUserResponse(CallContext.current().getCallingAccount().getDomainId(),
|
||||
result.first().toArray(new UserAccountJoinVO[result.first().size()]));
|
||||
response.setResponses(userResponses, result.second());
|
||||
return response;
|
||||
}
|
||||
|
||||
private Pair<List<UserAccountJoinVO>, Integer> searchForUsersInternal(ListUsersCmd cmd) throws PermissionDeniedException {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
|
||||
@ -427,42 +457,52 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
||||
}
|
||||
id = currentId;
|
||||
}
|
||||
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
|
||||
_accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), null, permittedAccounts, domainIdRecursiveListProject, listAll, false);
|
||||
Long domainId = domainIdRecursiveListProject.first();
|
||||
Boolean isRecursive = domainIdRecursiveListProject.second();
|
||||
ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
|
||||
|
||||
Filter searchFilter = new Filter(UserAccountJoinVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
|
||||
|
||||
Object username = cmd.getUsername();
|
||||
Object type = cmd.getAccountType();
|
||||
Object accountName = cmd.getAccountName();
|
||||
String accountName = cmd.getAccountName();
|
||||
Object state = cmd.getState();
|
||||
Object keyword = cmd.getKeyword();
|
||||
|
||||
Long domainId = cmd.getDomainId();
|
||||
boolean recursive = cmd.isRecursive();
|
||||
Long pageSizeVal = cmd.getPageSizeVal();
|
||||
Long startIndex = cmd.getStartIndex();
|
||||
|
||||
Filter searchFilter = new Filter(UserAccountJoinVO.class, "id", true, startIndex, pageSizeVal);
|
||||
|
||||
return getUserListInternal(caller, permittedAccounts, listAll, id, username, type, accountName, state, keyword, domainId, recursive, searchFilter);
|
||||
}
|
||||
|
||||
private Pair<List<UserAccountJoinVO>, Integer> getUserListInternal(Account caller, List<Long> permittedAccounts, boolean listAll, Long id, Object username, Object type,
|
||||
String accountName, Object state, Object keyword, Long domainId, boolean recursive, Filter searchFilter) {
|
||||
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, recursive, null);
|
||||
_accountMgr.buildACLSearchParameters(caller, id, accountName, null, permittedAccounts, domainIdRecursiveListProject, listAll, false);
|
||||
domainId = domainIdRecursiveListProject.first();
|
||||
Boolean isRecursive = domainIdRecursiveListProject.second();
|
||||
ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
|
||||
|
||||
SearchBuilder<UserAccountJoinVO> sb = _userAccountJoinDao.createSearchBuilder();
|
||||
_accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
|
||||
sb.and("username", sb.entity().getUsername(), SearchCriteria.Op.LIKE);
|
||||
sb.and("username", sb.entity().getUsername(), Op.LIKE);
|
||||
if (id != null && id == 1) {
|
||||
// system user should NOT be searchable
|
||||
List<UserAccountJoinVO> emptyList = new ArrayList<UserAccountJoinVO>();
|
||||
return new Pair<List<UserAccountJoinVO>, Integer>(emptyList, 0);
|
||||
} else if (id != null) {
|
||||
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
|
||||
sb.and("id", sb.entity().getId(), Op.EQ);
|
||||
} else {
|
||||
// this condition is used to exclude system user from the search
|
||||
// results
|
||||
sb.and("id", sb.entity().getId(), SearchCriteria.Op.NEQ);
|
||||
sb.and("id", sb.entity().getId(), Op.NEQ);
|
||||
}
|
||||
|
||||
sb.and("type", sb.entity().getAccountType(), SearchCriteria.Op.EQ);
|
||||
sb.and("domainId", sb.entity().getDomainId(), SearchCriteria.Op.EQ);
|
||||
sb.and("accountName", sb.entity().getAccountName(), SearchCriteria.Op.EQ);
|
||||
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
|
||||
sb.and("type", sb.entity().getAccountType(), Op.EQ);
|
||||
sb.and("domainId", sb.entity().getDomainId(), Op.EQ);
|
||||
sb.and("accountName", sb.entity().getAccountName(), Op.EQ);
|
||||
sb.and("state", sb.entity().getState(), Op.EQ);
|
||||
|
||||
if ((accountName == null) && (domainId != null)) {
|
||||
sb.and("domainPath", sb.entity().getDomainPath(), SearchCriteria.Op.LIKE);
|
||||
sb.and("domainPath", sb.entity().getDomainPath(), Op.LIKE);
|
||||
}
|
||||
|
||||
SearchCriteria<UserAccountJoinVO> sc = sb.create();
|
||||
@ -472,15 +512,15 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
||||
|
||||
if (keyword != null) {
|
||||
SearchCriteria<UserAccountJoinVO> ssc = _userAccountJoinDao.createSearchCriteria();
|
||||
ssc.addOr("username", SearchCriteria.Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("firstname", SearchCriteria.Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("lastname", SearchCriteria.Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("email", SearchCriteria.Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("state", SearchCriteria.Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("accountName", SearchCriteria.Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("accountType", SearchCriteria.Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("username", Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("firstname", Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("lastname", Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("email", Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("state", Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("accountName", Op.LIKE, "%" + keyword + "%");
|
||||
ssc.addOr("accountType", Op.LIKE, "%" + keyword + "%");
|
||||
|
||||
sc.addAnd("username", SearchCriteria.Op.SC, ssc);
|
||||
sc.addAnd("username", Op.SC, ssc);
|
||||
}
|
||||
|
||||
if (username != null) {
|
||||
|
||||
189
test/integration/plugins/ldap/ldap_test_data.py
Normal file
189
test/integration/plugins/ldap/ldap_test_data.py
Normal file
@ -0,0 +1,189 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
|
||||
class LdapTestData:
|
||||
#constants
|
||||
configuration = "ldap_configuration"
|
||||
syncAccounts = "accountsToSync"
|
||||
parentDomain = "LDAP"
|
||||
manualDomain = "manual"
|
||||
importDomain = "import"
|
||||
syncDomain = "sync"
|
||||
name = "name"
|
||||
id = "id"
|
||||
notAvailable = "N/A"
|
||||
groups = "groups"
|
||||
group = "group"
|
||||
seniorAccount = "seniors"
|
||||
juniorAccount = "juniors"
|
||||
|
||||
ldap_ip_address = "localhost"
|
||||
ldap_port = 389
|
||||
hostname = "hostname"
|
||||
port = "port"
|
||||
dn = "dn"
|
||||
ou = "ou"
|
||||
cn = "cn"
|
||||
member = "uniqueMember"
|
||||
basedn = "basedn"
|
||||
basednConfig = "ldap.basedn"
|
||||
ldapPw = "ldapPassword"
|
||||
ldapPwConfig = "ldap.bind.password"
|
||||
principal = "ldapUsername"
|
||||
principalConfig = "ldap.bind.principal"
|
||||
users = "users"
|
||||
objectClass = "objectClass"
|
||||
sn = "sn"
|
||||
givenName = "givenName"
|
||||
uid = "uid"
|
||||
domains = "domains"
|
||||
type = "accounttype"
|
||||
password = "userPassword"
|
||||
mail = "email"
|
||||
groupPrinciple = "ldap.search.group.principle"
|
||||
|
||||
basednValue = "dc=echt,dc=net"
|
||||
people_dn = "ou=people,"+basednValue
|
||||
groups_dn = "ou=groups,"+basednValue
|
||||
admins = "ou=admins,"+groups_dn
|
||||
juniors = "ou=juniors,"+groups_dn
|
||||
seniors = "ou=seniors,"+groups_dn
|
||||
userObject = "userObject"
|
||||
usernameAttribute = "usernameAttribute"
|
||||
memberAttribute = "memberAttribute"
|
||||
mailAttribute = "emailAttribute"
|
||||
|
||||
def __init__(self):
|
||||
self.testdata = {
|
||||
LdapTestData.configuration: {
|
||||
LdapTestData.mailAttribute: "mail",
|
||||
LdapTestData.userObject: "person",
|
||||
LdapTestData.usernameAttribute: LdapTestData.uid,
|
||||
LdapTestData.memberAttribute: LdapTestData.member,
|
||||
# global values for use in all domains
|
||||
LdapTestData.hostname: LdapTestData.ldap_ip_address,
|
||||
LdapTestData.port: LdapTestData.ldap_port,
|
||||
LdapTestData.basedn: LdapTestData.basednValue,
|
||||
LdapTestData.ldapPw: "secret",
|
||||
LdapTestData.principal: "cn=willie,"+LdapTestData.basednValue,
|
||||
},
|
||||
LdapTestData.groups: [
|
||||
{
|
||||
LdapTestData.dn : LdapTestData.people_dn,
|
||||
LdapTestData.objectClass: ["organizationalUnit", "top"],
|
||||
LdapTestData.ou : "People"
|
||||
},
|
||||
{
|
||||
LdapTestData.dn : LdapTestData.groups_dn,
|
||||
LdapTestData.objectClass: ["organizationalUnit", "top"],
|
||||
LdapTestData.ou : "Groups"
|
||||
},
|
||||
{
|
||||
LdapTestData.dn : LdapTestData.seniors,
|
||||
LdapTestData.objectClass: ["groupOfUniqueNames", "top"],
|
||||
LdapTestData.ou : "seniors",
|
||||
LdapTestData.cn : "seniors",
|
||||
LdapTestData.member : ["uid=bobby,ou=people,"+LdapTestData.basednValue, "uid=rohit,ou=people,"+LdapTestData.basednValue]
|
||||
},
|
||||
{
|
||||
LdapTestData.dn : LdapTestData.juniors,
|
||||
LdapTestData.objectClass : ["groupOfUniqueNames", "top"],
|
||||
LdapTestData.ou : "juniors",
|
||||
LdapTestData.cn : "juniors",
|
||||
LdapTestData.member : ["uid=dahn,ou=people,"+LdapTestData.basednValue, "uid=paul,ou=people,"+LdapTestData.basednValue]
|
||||
}
|
||||
],
|
||||
LdapTestData.users: [
|
||||
{
|
||||
LdapTestData.dn : "uid=bobby,ou=people,"+LdapTestData.basednValue,
|
||||
LdapTestData.objectClass : ["inetOrgPerson", "top", "person"],
|
||||
LdapTestData.cn : "bobby",
|
||||
LdapTestData.sn: "Stoyanov",
|
||||
LdapTestData.givenName : "Boris",
|
||||
LdapTestData.uid : "bobby",
|
||||
LdapTestData.mail: "bobby@echt.net"
|
||||
},
|
||||
{
|
||||
LdapTestData.dn : "uid=dahn,ou=people,"+LdapTestData.basednValue,
|
||||
LdapTestData.objectClass : ["inetOrgPerson", "top", "person"],
|
||||
LdapTestData.cn : "dahn",
|
||||
LdapTestData.sn: "Hoogland",
|
||||
LdapTestData.givenName : "Daan",
|
||||
LdapTestData.uid : "dahn",
|
||||
LdapTestData.mail: "dahn@echt.net"
|
||||
},
|
||||
{
|
||||
LdapTestData.dn : "uid=paul,ou=people,"+LdapTestData.basednValue,
|
||||
LdapTestData.objectClass : ["inetOrgPerson", "top", "person"],
|
||||
LdapTestData.cn : "Paul",
|
||||
LdapTestData.sn: "Angus",
|
||||
LdapTestData.givenName : "Paul",
|
||||
LdapTestData.uid : "paul",
|
||||
LdapTestData.mail: "paul@echt.net"
|
||||
},
|
||||
{
|
||||
LdapTestData.dn : "uid=rohit,ou=people,"+LdapTestData.basednValue,
|
||||
LdapTestData.objectClass : ["inetOrgPerson", "top", "person"],
|
||||
LdapTestData.cn : "rhtyd",
|
||||
LdapTestData.sn: "Yadav",
|
||||
LdapTestData.givenName : "Rohit",
|
||||
LdapTestData.uid : "rohit",
|
||||
LdapTestData.mail: "rhtyd@echt.net"
|
||||
},
|
||||
# extra test user (just in case)
|
||||
# {
|
||||
# LdapTestData.dn : "uid=noone,ou=people,"+LdapTestData.basednValue,
|
||||
# LdapTestData.objectClass : ["inetOrgPerson", "person"],
|
||||
# LdapTestData.cn : "noone",
|
||||
# LdapTestData.sn: "a User",
|
||||
# LdapTestData.givenName : "Not",
|
||||
# LdapTestData.uid : "noone",
|
||||
# LdapTestData.mail: "noone@echt.net",
|
||||
# LdapTestData.password: 'password'
|
||||
# },
|
||||
],
|
||||
LdapTestData.domains : [
|
||||
{
|
||||
LdapTestData.name : LdapTestData.parentDomain,
|
||||
LdapTestData.id : LdapTestData.notAvailable
|
||||
},
|
||||
{
|
||||
LdapTestData.name : LdapTestData.manualDomain,
|
||||
LdapTestData.id : LdapTestData.notAvailable
|
||||
},
|
||||
{
|
||||
LdapTestData.name : LdapTestData.importDomain,
|
||||
LdapTestData.id : LdapTestData.notAvailable
|
||||
},
|
||||
{
|
||||
LdapTestData.name : LdapTestData.syncDomain,
|
||||
LdapTestData.id : LdapTestData.notAvailable
|
||||
},
|
||||
],
|
||||
LdapTestData.syncAccounts : [
|
||||
{
|
||||
LdapTestData.name : LdapTestData.juniorAccount,
|
||||
LdapTestData.type : 0,
|
||||
LdapTestData.group : LdapTestData.juniors
|
||||
},
|
||||
{
|
||||
LdapTestData.name : LdapTestData.seniorAccount,
|
||||
LdapTestData.type : 2,
|
||||
LdapTestData.group : LdapTestData.seniors
|
||||
}
|
||||
],
|
||||
}
|
||||
476
test/integration/plugins/ldap/test_ldap.py
Normal file
476
test/integration/plugins/ldap/test_ldap.py
Normal file
@ -0,0 +1,476 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
|
||||
#Import Local Modules
|
||||
from .ldap_test_data import LdapTestData
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.lib.utils import (cleanup_resources)
|
||||
from marvin.lib.base import (listLdapUsers,
|
||||
ldapCreateAccount,
|
||||
importLdapUsers,
|
||||
User,
|
||||
Domain,
|
||||
Account,
|
||||
addLdapConfiguration,
|
||||
deleteLdapConfiguration,
|
||||
linkAccountToLdap,
|
||||
linkDomainToLdap,
|
||||
updateConfiguration)
|
||||
from marvin.lib.common import (get_domain,
|
||||
get_zone)
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
# for login validation
|
||||
import requests
|
||||
|
||||
import logging
|
||||
|
||||
class TestLDAP(cloudstackTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
'''
|
||||
needs to
|
||||
- create the applicable ldap accounts in the directory server
|
||||
- create three domains:
|
||||
-- LDAP/manual
|
||||
-- LDAP/import
|
||||
-- LDAP/sync
|
||||
'''
|
||||
cls.logger = logging.getLogger(__name__)
|
||||
stream_handler = logging.StreamHandler()
|
||||
logger_formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
|
||||
stream_handler.setFormatter(logger_formatter)
|
||||
cls.logger.setLevel(logging.DEBUG)
|
||||
cls.logger.addHandler(stream_handler)
|
||||
|
||||
cls.logger.info("Setting up Class")
|
||||
testClient = super(TestLDAP, cls).getClsTestClient()
|
||||
cls.apiclient = testClient.getApiClient()
|
||||
|
||||
try:
|
||||
# Setup test data
|
||||
cls.testdata = LdapTestData()
|
||||
if cls.config.TestData and cls.config.TestData.Path:
|
||||
cls.logger.debug("reading extra config from '" + cls.config.TestData.Path + "'")
|
||||
cls.testdata.update(cls.config.TestData.Path)
|
||||
cls.logger.debug(cls.testdata)
|
||||
|
||||
cls.services = testClient.getParsedTestDataConfig()
|
||||
cls.services["configurableData"]["ldap_configuration"] = cls.testdata.testdata["ldap_configuration"]
|
||||
cls.logger.debug(cls.services["configurableData"]["ldap_configuration"])
|
||||
|
||||
# Get Zone, Domain
|
||||
cls.domain = get_domain(cls.apiclient)
|
||||
cls.logger.debug("standard domain: %s" % cls.domain.id)
|
||||
cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
|
||||
|
||||
cls._cleanup = []
|
||||
|
||||
# Build the test env
|
||||
cls.create_domains(cls.testdata)
|
||||
cls.configure_ldap_for_domains(cls.testdata)
|
||||
|
||||
cls.test_user = [
|
||||
cls.testdata.testdata[LdapTestData.users][0][LdapTestData.uid],
|
||||
cls.testdata.testdata[LdapTestData.users][1][LdapTestData.uid],
|
||||
cls.testdata.testdata[LdapTestData.users][2][LdapTestData.uid]
|
||||
]
|
||||
except Exception as e:
|
||||
cls.logger.debug("Exception in setUpClass(cls): %s" % e)
|
||||
cls.tearDownClass()
|
||||
raise Exception("setup failed due to %s", e)
|
||||
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.logger.info("Tearing Down Class")
|
||||
try:
|
||||
cleanup_resources(cls.apiclient, reversed(cls._cleanup))
|
||||
cls.remove_ldap_configuration_for_domains()
|
||||
cls.logger.debug("done cleaning up resources in tearDownClass(cls) %s")
|
||||
except Exception as e:
|
||||
cls.logger.debug("Exception in tearDownClass(cls): %s" % e)
|
||||
|
||||
def setUp(self):
|
||||
self.cleanup = []
|
||||
|
||||
self.server_details = self.config.__dict__["mgtSvr"][0].__dict__
|
||||
self.server_url = "http://%s:8080/client/api" % self.server_details['mgtSvrIp']
|
||||
|
||||
return
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
cleanup_resources(self.apiclient, self.cleanup)
|
||||
except Exception as e:
|
||||
raise Exception("Warning: Exception during cleanup : %s" % e)
|
||||
return
|
||||
|
||||
@attr(tags=["smoke", "advanced"], required_hardware="false")
|
||||
def test_01_manual(self):
|
||||
'''
|
||||
test if an account can be imported
|
||||
|
||||
prerequisite
|
||||
a ldap host is configured
|
||||
a domain is linked to cloudstack
|
||||
'''
|
||||
cmd = listLdapUsers.listLdapUsersCmd()
|
||||
cmd.domainid = self.manualDomain.id
|
||||
cmd.userfilter = "LocalDomain"
|
||||
response = self.apiclient.listLdapUsers(cmd)
|
||||
self.logger.info("users found for linked domain %s" % response)
|
||||
self.assertEqual(len(response), len(self.testdata.testdata[LdapTestData.users]), "unexpected number (%d) of ldap users" % len(self.testdata.testdata[LdapTestData.users]))
|
||||
|
||||
cmd = ldapCreateAccount.ldapCreateAccountCmd()
|
||||
cmd.domainid = self.manualDomain.id
|
||||
cmd.accounttype = 0
|
||||
cmd.username = self.test_user[1]
|
||||
create_response = self.apiclient.ldapCreateAccount(cmd)
|
||||
|
||||
# cleanup
|
||||
# last results id should be the account
|
||||
list_response = Account.list(self.apiclient, id=create_response.id)
|
||||
account_created = Account(list_response[0].__dict__)
|
||||
self.cleanup.append(account_created)
|
||||
|
||||
self.assertEqual(len(create_response.user), 1, "only one user %s should be present" % self.test_user[1])
|
||||
|
||||
self.assertEqual(len(list_response),
|
||||
1,
|
||||
"only one account (for user %s) should be present" % self.test_user[1])
|
||||
|
||||
return
|
||||
|
||||
@attr(tags=["smoke", "advanced"], required_hardware="false")
|
||||
def test_02_import(self):
|
||||
'''
|
||||
test if components are synced
|
||||
|
||||
prerequisite
|
||||
a ldap host is configured
|
||||
a domain is linked to cloudstack
|
||||
'''
|
||||
domainid = self.importDomain.id
|
||||
|
||||
cmd = importLdapUsers.importLdapUsersCmd()
|
||||
cmd.domainid = domainid
|
||||
cmd.accounttype = 0
|
||||
import_response = self.apiclient.importLdapUsers(cmd)
|
||||
|
||||
# this is needed purely for cleanup:
|
||||
# cleanup
|
||||
list_response = Account.list(self.apiclient, domainid=domainid)
|
||||
for account in list_response:
|
||||
account_created = Account(account.__dict__)
|
||||
self.logger.debug("account to clean: %s (id: %s)" % (account_created.name, account_created.id))
|
||||
self.cleanup.append(account_created)
|
||||
|
||||
self.assertEqual(len(import_response), len(self.testdata.testdata[LdapTestData.users]), "unexpected number of ldap users")
|
||||
|
||||
self.assertEqual(len(list_response), len(self.testdata.testdata[LdapTestData.users]), "only one account (for user %s) should be present" % self.test_user[1])
|
||||
|
||||
return
|
||||
|
||||
@attr(tags=["smoke", "advanced"], required_hardware="false")
|
||||
def test_03_sync(self):
|
||||
'''
|
||||
test if components are synced
|
||||
|
||||
prerequisite
|
||||
a ldap host is configured
|
||||
a domain is linked to cloudstack
|
||||
some accounts in that domain are linked to groups in ldap
|
||||
'''
|
||||
domainid = self.syncDomain.id
|
||||
username = self.test_user[1]
|
||||
|
||||
# validate the user doesn't exist
|
||||
response = User.list(self.apiclient,domainid=domainid,username=username)
|
||||
self.assertEqual(response, None, "user should not exist yet")
|
||||
|
||||
self.logon_test_user(username)
|
||||
|
||||
# now validate the user exists in domain
|
||||
response = User.list(self.apiclient,domainid=domainid,username=username)
|
||||
for user in response:
|
||||
user_created = User(user.__dict__)
|
||||
self.logger.debug("user to clean: %s (id: %s)" % (user_created.username, user_created.id))
|
||||
self.cleanup.append(user_created)
|
||||
|
||||
# now verify the creation of the user
|
||||
self.assertEqual(len(response), 1, "user should exist by now")
|
||||
|
||||
return
|
||||
|
||||
@attr(tags=["smoke", "advanced"], required_hardware="false")
|
||||
def test_04_filtered_list_of_users(self):
|
||||
'''
|
||||
test if we can get a filtered list of ldap users
|
||||
|
||||
prerequisite
|
||||
a ldap host is configured
|
||||
a couple of ldapdomains are linked to cloudstack domains
|
||||
some accounts in those domain are linked to groups in ldap
|
||||
some ldap accounts are linked and present with the same uid
|
||||
some ldap accounts are not yet linked but present at other locations in cloudstack
|
||||
|
||||
NOTE 1: if this test is run last only the explicitely imported test user from test_03_sync
|
||||
is in the system. The accounts from test_01_manual and test_02_import should have been cleared
|
||||
by the test tearDown(). We can not depend on test_03_sync having run so the test must avoid
|
||||
depending on it either being available or not.
|
||||
|
||||
NOTE 2: this test will not work if the ldap users UIDs are already present in the ACS instance
|
||||
against which is being tested
|
||||
'''
|
||||
cmd = listLdapUsers.listLdapUsersCmd()
|
||||
cmd.userfilter = "NoFilter"
|
||||
cmd.domainid = self.manualDomain.id
|
||||
response = self.apiclient.listLdapUsers(cmd)
|
||||
self.logger.debug(cmd.userfilter + " : " + str(response))
|
||||
self.assertEqual(len(response), len(self.testdata.testdata[LdapTestData.users]), "unexpected number of ldap users")
|
||||
|
||||
# create a non ldap user with the uid of cls.test_user[0] in parentDomain
|
||||
# create a manual import of a cls.test_user[1] in manualDomain
|
||||
# log on with test_user[2] in an syncDomain
|
||||
|
||||
# we can now test all four filtertypes in syncDomain and inspect the respective outcomes for validity
|
||||
|
||||
self.logon_test_user(self.test_user[2])
|
||||
|
||||
cmd.userfilter = "LocalDomain"
|
||||
cmd.domainid = self.syncDomain.id
|
||||
response = self.apiclient.listLdapUsers(cmd)
|
||||
self.logger.debug(cmd.userfilter + " : " + str(response))
|
||||
self.assertEqual(len(response),
|
||||
len(self.testdata.testdata[LdapTestData.users]) - 1,
|
||||
"unexpected number of ldap users")
|
||||
|
||||
@attr(tags=["smoke", "advanced"], required_hardware="false")
|
||||
def test_05_relink_account_and_reuse_user(self):
|
||||
'''
|
||||
test if an account and thus a user can be removed and re-added
|
||||
|
||||
test if components still are synced
|
||||
|
||||
prerequisite
|
||||
a ldap host is configured
|
||||
a domain is linked to cloudstack
|
||||
some accounts in that domain are linked to groups in ldap
|
||||
'''
|
||||
domainid = self.syncDomain.id
|
||||
username = self.test_user[1]
|
||||
|
||||
# validate the user doesn't exist
|
||||
response = User.list(self.apiclient,domainid=domainid,username=username)
|
||||
self.assertEqual(response, None, "user should not exist yet")
|
||||
|
||||
self.logon_test_user(username)
|
||||
|
||||
# now validate the user exists in domain
|
||||
response = User.list(self.apiclient,domainid=domainid,username=username)
|
||||
# for user in response:
|
||||
# user_created = User(user.__dict__)
|
||||
# self.debug("user to clean: %s (id: %s)" % (user_created.username, user_created.id))
|
||||
# # we don't cleanup to test if re-adding fails
|
||||
# self.cleanup.append(user_created)
|
||||
|
||||
# now verify the creation of the user
|
||||
self.assertEqual(len(response), 1, "user should exist by now")
|
||||
|
||||
# delete the account - quick implementation: user[1] happens to be a junior
|
||||
self.junior_account.delete(self.apiclient)
|
||||
|
||||
# add the account with the same ldap group
|
||||
self.bind_account_to_ldap(
|
||||
account=self.testdata.testdata[LdapTestData.syncAccounts][0]["name"],
|
||||
ldapdomain=self.testdata.testdata[LdapTestData.syncAccounts][0]["group"],
|
||||
accounttype=self.testdata.testdata[LdapTestData.syncAccounts][0]["accounttype"])
|
||||
|
||||
# logon the user - should succeed - reported to fail
|
||||
self.logon_test_user(username)
|
||||
|
||||
# now verify the creation of the user
|
||||
response = User.list(self.apiclient,domainid=domainid,username=username)
|
||||
for user in response:
|
||||
user_created = User(user.__dict__)
|
||||
self.debug("user to clean: %s (id: %s)" % (user_created.username, user_created.id))
|
||||
# we don't cleanup to test if re-adding fails
|
||||
# self.cleanup.append(user_created)
|
||||
self.assertEqual(len(response), 1, "user should exist again")
|
||||
return
|
||||
|
||||
|
||||
def logon_test_user(self, username, domain = None):
|
||||
# login of dahn should create a user in account juniors
|
||||
args = {}
|
||||
args["command"] = 'login'
|
||||
args["username"] = username
|
||||
args["password"] = 'password'
|
||||
if domain == None:
|
||||
args["domain"] = "/" + self.parentDomain.name + "/" + self.syncDomain.name
|
||||
else:
|
||||
args["domain"] = domain
|
||||
args["response"] = "json"
|
||||
session = requests.Session()
|
||||
try:
|
||||
resp = session.post(self.server_url, params=args, verify=False)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
self.fail("Failed to attempt login request to mgmt server")
|
||||
|
||||
|
||||
@classmethod
|
||||
def create_domains(cls, td):
|
||||
# create a parent domain
|
||||
cls.parentDomain = cls.create_domain(td.testdata["domains"][0], parent_domain=cls.domain.id)
|
||||
cls.manualDomain = cls.create_domain(td.testdata["domains"][1], parent_domain=cls.parentDomain.id)
|
||||
cls.importDomain = cls.create_domain(td.testdata["domains"][2], parent_domain=cls.parentDomain.id)
|
||||
cls.syncDomain = cls.create_domain(td.testdata["domains"][3], parent_domain=cls.parentDomain.id)
|
||||
|
||||
@classmethod
|
||||
def create_domain(cls, domain_to_create, parent_domain = None):
|
||||
cls.logger.debug("Creating domain: %s under %s" % (domain_to_create[LdapTestData.name], parent_domain))
|
||||
if parent_domain:
|
||||
domain_to_create["parentdomainid"] = parent_domain
|
||||
tmpDomain = Domain.create(cls.apiclient, domain_to_create)
|
||||
cls.logger.debug("Created domain %s with id %s " % (tmpDomain.name, tmpDomain.id))
|
||||
cls._cleanup.append(tmpDomain)
|
||||
return tmpDomain
|
||||
|
||||
@classmethod
|
||||
def configure_ldap_for_domains(cls, td) :
|
||||
cmd = addLdapConfiguration.addLdapConfigurationCmd()
|
||||
cmd.hostname = td.testdata[LdapTestData.configuration][LdapTestData.hostname]
|
||||
cmd.port = td.testdata[LdapTestData.configuration][LdapTestData.port]
|
||||
|
||||
cls.logger.debug("configuring ldap server for domain %s" % LdapTestData.manualDomain)
|
||||
cmd.domainid = cls.manualDomain.id
|
||||
response = cls.apiclient.addLdapConfiguration(cmd)
|
||||
cls.manualLdap = response
|
||||
|
||||
cls.logger.debug("configuring ldap server for domain %s" % LdapTestData.importDomain)
|
||||
cmd.domainid = cls.importDomain.id
|
||||
response = cls.apiclient.addLdapConfiguration(cmd)
|
||||
cls.importLdap = response
|
||||
|
||||
cls.logger.debug("configuring ldap server for domain %s" % LdapTestData.syncDomain)
|
||||
cmd.domainid = cls.syncDomain.id
|
||||
response = cls.apiclient.addLdapConfiguration(cmd)
|
||||
cls.syncLdap = response
|
||||
|
||||
cls.set_ldap_settings_on_domain(domainid=cls.manualDomain.id)
|
||||
cls.bind_domain_to_ldap(domainid=cls.manualDomain.id, ldapdomain=cls.testdata.admins)
|
||||
|
||||
cls.set_ldap_settings_on_domain(domainid=cls.importDomain.id)
|
||||
cls.bind_domain_to_ldap(domainid=cls.importDomain.id, ldapdomain=cls.testdata.admins,
|
||||
accounttype=2, type="Group") # just to be testing different types
|
||||
|
||||
cls.set_ldap_settings_on_domain(domainid=cls.syncDomain.id)
|
||||
cls.create_sync_accounts()
|
||||
|
||||
@classmethod
|
||||
def remove_ldap_configuration_for_domains(cls) :
|
||||
cls.logger.debug("deleting configurations for ldap server")
|
||||
cmd = deleteLdapConfiguration.deleteLdapConfigurationCmd()
|
||||
|
||||
cmd.hostname = cls.manualLdap.hostname
|
||||
cmd.port = cls.manualLdap.port
|
||||
cmd.domainid = cls.manualLdap.domainid
|
||||
response = cls.apiclient.deleteLdapConfiguration(cmd)
|
||||
cls.logger.debug("configuration deleted for %s" % response)
|
||||
|
||||
cmd.hostname = cls.importLdap.hostname
|
||||
cmd.port = cls.importLdap.port
|
||||
cmd.domainid = cls.importLdap.domainid
|
||||
response = cls.apiclient.deleteLdapConfiguration(cmd)
|
||||
cls.logger.debug("configuration deleted for %s" % response)
|
||||
|
||||
cmd.hostname = cls.syncLdap.hostname
|
||||
cmd.port = cls.syncLdap.port
|
||||
cmd.domainid = cls.syncLdap.domainid
|
||||
cls.logger.debug("deleting configuration %s" % cmd)
|
||||
response = cls.apiclient.deleteLdapConfiguration(cmd)
|
||||
cls.logger.debug("configuration deleted for %s" % response)
|
||||
|
||||
|
||||
@classmethod
|
||||
def create_sync_accounts(cls):
|
||||
cls.logger.debug("creating account: %s" % LdapTestData.seniors)
|
||||
cls.senior_account = cls.bind_account_to_ldap(
|
||||
account=cls.testdata.testdata[LdapTestData.syncAccounts][1]["name"],
|
||||
ldapdomain=cls.testdata.testdata[LdapTestData.syncAccounts][1]["group"],
|
||||
accounttype=cls.testdata.testdata[LdapTestData.syncAccounts][1]["accounttype"])
|
||||
cls.junior_account = cls.bind_account_to_ldap(
|
||||
account=cls.testdata.testdata[LdapTestData.syncAccounts][0]["name"],
|
||||
ldapdomain=cls.testdata.testdata[LdapTestData.syncAccounts][0]["group"],
|
||||
accounttype=cls.testdata.testdata[LdapTestData.syncAccounts][0]["accounttype"])
|
||||
|
||||
@classmethod
|
||||
def bind_account_to_ldap(cls, account, ldapdomain, type="Group", accounttype=0):
|
||||
cmd = linkAccountToLdap.linkAccountToLdapCmd()
|
||||
|
||||
cmd.domainid = cls.syncDomain.id
|
||||
cmd.account = account
|
||||
cmd.ldapdomain = ldapdomain
|
||||
cmd.type = type
|
||||
cmd.accounttype = accounttype
|
||||
|
||||
response = cls.apiclient.linkAccountToLdap(cmd)
|
||||
cls.logger.info("account linked to ladp %s" % response)
|
||||
|
||||
# this is needed purely for cleanup:
|
||||
response = Account.list(cls.apiclient, id=response.accountid)
|
||||
account_created = Account(response[0].__dict__)
|
||||
cls._cleanup.append(account_created)
|
||||
return account_created
|
||||
|
||||
@classmethod
|
||||
def bind_domain_to_ldap(cls, domainid, ldapdomain, type="OU", accounttype=0):
|
||||
cmd = linkDomainToLdap.linkDomainToLdapCmd()
|
||||
cmd.domainid = domainid
|
||||
cmd.type = type
|
||||
cmd.accounttype = accounttype
|
||||
cmd.ldapdomain = ldapdomain
|
||||
response = cls.apiclient.linkDomainToLdap(cmd)
|
||||
cls.logger.info("domain linked to ladp %s" % response)
|
||||
|
||||
@classmethod
|
||||
def set_ldap_settings_on_domain(cls, domainid):
|
||||
cmd = updateConfiguration.updateConfigurationCmd()
|
||||
cmd.domainid = domainid
|
||||
cmd.name = LdapTestData.basednConfig
|
||||
cmd.value = cls.testdata.testdata[LdapTestData.configuration][LdapTestData.basedn]
|
||||
response = cls.apiclient.updateConfiguration(cmd)
|
||||
cls.logger.debug("set the basedn: %s" % response)
|
||||
cmd.name = LdapTestData.ldapPwConfig
|
||||
cmd.value = cls.testdata.testdata[LdapTestData.configuration][LdapTestData.ldapPw]
|
||||
response = cls.apiclient.updateConfiguration(cmd)
|
||||
cls.logger.debug("set the pw: %s" % response)
|
||||
cmd.name = LdapTestData.principalConfig
|
||||
cmd.value = cls.testdata.testdata[LdapTestData.configuration][LdapTestData.principal]
|
||||
response = cls.apiclient.updateConfiguration(cmd)
|
||||
cls.logger.debug("set the id: %s" % response)
|
||||
if cls.testdata.testdata[LdapTestData.configuration].has_key(LdapTestData.groupPrinciple) :
|
||||
cmd.name = LdapTestData.groupPrinciple
|
||||
cmd.value = cls.testdata.testdata[LdapTestData.configuration][LdapTestData.groupPrinciple]
|
||||
response = cls.apiclient.updateConfiguration(cmd)
|
||||
cls.logger.debug("set the id: %s" % response)
|
||||
|
||||
|
||||
## python ldap utility functions
|
||||
@ -181,7 +181,7 @@ class Account:
|
||||
self.__dict__.update(items)
|
||||
|
||||
@classmethod
|
||||
def create(cls, apiclient, services, admin=False, domainid=None, roleid=None):
|
||||
def create(cls, apiclient, services, admin=False, domainid=None, roleid=None, account=None):
|
||||
"""Creates an account"""
|
||||
cmd = createAccount.createAccountCmd()
|
||||
|
||||
@ -213,6 +213,9 @@ class Account:
|
||||
if roleid:
|
||||
cmd.roleid = roleid
|
||||
|
||||
if account:
|
||||
cmd.account = account
|
||||
|
||||
account = apiclient.createAccount(cmd)
|
||||
|
||||
return Account(account.__dict__)
|
||||
|
||||
@ -639,6 +639,7 @@
|
||||
<th><translate key="label.name"/></th>
|
||||
<th><translate key="label.username"/></th>
|
||||
<th><translate key="label.email"/></th>
|
||||
<th><translate key="label.user.conflict"/></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@ -1759,8 +1759,10 @@ var dictionary = {
|
||||
"label.use.vm.ips":"Use VM IPs",
|
||||
"label.used":"Used",
|
||||
"label.user":"User",
|
||||
"label.user.conflict":"Conflict",
|
||||
"label.user.data":"User Data",
|
||||
"label.user.details":"User details",
|
||||
"label.user.source":"source",
|
||||
"label.user.vm":"User VM",
|
||||
"label.username":"Username",
|
||||
"label.username.lower":"username",
|
||||
|
||||
@ -68,10 +68,43 @@
|
||||
required: true
|
||||
},
|
||||
docID: 'helpAccountLastName'
|
||||
}
|
||||
},
|
||||
conflictingusersource: {
|
||||
label: 'label.user.conflict',
|
||||
validation: {
|
||||
required: true
|
||||
},
|
||||
docID: 'helpConflictSource'
|
||||
}
|
||||
},
|
||||
|
||||
informationNotInLdap: {
|
||||
filter: {
|
||||
label: 'label.filterBy',
|
||||
docID: 'helpLdapUserFilter',
|
||||
select: function(args) {
|
||||
var items = [];
|
||||
items.push({
|
||||
id: "NoFilter",
|
||||
description: "No filter"
|
||||
});
|
||||
items.push({
|
||||
id: "LocalDomain",
|
||||
description: "Local domain"
|
||||
});
|
||||
items.push({
|
||||
id: "AnyDomain",
|
||||
description: "Any domain"
|
||||
});
|
||||
items.push({
|
||||
id: "PotentialImport",
|
||||
description: "Potential import"
|
||||
});
|
||||
args.response.success({
|
||||
data: items
|
||||
});
|
||||
}
|
||||
},
|
||||
domainid: {
|
||||
label: 'label.domain',
|
||||
docID: 'helpAccountDomain',
|
||||
|
||||
@ -110,6 +110,11 @@ cloudStack.docs = {
|
||||
},
|
||||
|
||||
//Ldap
|
||||
helpLdapUserFilter: {
|
||||
desc: 'Filter to apply to listing of ldap accounts\n\t"NoFilter": no filtering is done\n\t"LocalDomain": shows only users not in the current or requested domain\n\t"AnyDomain": shows only users not currently known to cloudstack (in any domain)\n\t"PotentialImport": shows all users that (would be) automatically imported to cloudstack with their current usersource',
|
||||
externalLink: ''
|
||||
},
|
||||
|
||||
helpLdapQueryFilter: {
|
||||
|
||||
desc: 'Query filter is used to find a mapped user in the external LDAP server.Cloudstack provides some wildchars to represent the unique attributes in its database . Example - If Cloudstack account-name is same as the LDAP uid, then following will be a valid filter: Queryfilter : (&(uid=%u) , Queryfilter: .incase of Active Directory , Email _ID :(&(mail=%e)) , displayName :(&(displayName=%u)',
|
||||
@ -127,7 +132,7 @@ cloudStack.docs = {
|
||||
|
||||
helpIPReservationNetworkCidr: {
|
||||
desc: 'The CIDR of the entire network when IP reservation is configured',
|
||||
externalLink: ' '
|
||||
externalLink: ''
|
||||
|
||||
},
|
||||
|
||||
|
||||
@ -111,6 +111,14 @@
|
||||
});
|
||||
|
||||
if (ldapStatus) {
|
||||
var userFilter = $wizard.find('#label_filterBy').val();
|
||||
if (userFilter == null) {
|
||||
userFilter = 'AnyDomain';
|
||||
}
|
||||
var domainId = $wizard.find('#label_domain').val();
|
||||
if (domainId == null) {
|
||||
domainId = $.cookie('domainid');
|
||||
}
|
||||
var $table = $wizard.find('.ldap-account-choice tbody');
|
||||
$("#label_ldap_group_name").on("keypress", function(event) {
|
||||
if ($table.find("#tr-groupname-message").length === 0) {
|
||||
@ -125,94 +133,13 @@
|
||||
$table.find("#tr-groupname-message").hide();
|
||||
}
|
||||
});
|
||||
$.ajax({
|
||||
url: createURL("listLdapUsers&listtype=new"),
|
||||
loadList = function() { $.ajax({
|
||||
url: createURL("listLdapUsers&listtype=new&domainid=" + domainId + "&userfilter=" + userFilter),
|
||||
dataType: "json",
|
||||
async: false,
|
||||
success: function(json) {
|
||||
//for testing only (begin)
|
||||
/*
|
||||
json = {
|
||||
"ldapuserresponse": {
|
||||
"count": 11,
|
||||
"LdapUser": [
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"principal": "CN=Administrator,CN=Users,DC=hyd-qa,DC=com",
|
||||
"username": "Administrator",
|
||||
"domain": "CN=Administrator"
|
||||
},
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"principal": "CN=Guest,CN=Users,DC=hyd-qa,DC=com",
|
||||
"username": "Guest",
|
||||
"domain": "CN=Guest"
|
||||
},
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"principal": "CN=IUSR_HYD-QA12,CN=Users,DC=hyd-qa,DC=com",
|
||||
"username": "IUSR_HYD-QA12",
|
||||
"domain": "CN=IUSR_HYD-QA12"
|
||||
},
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"principal": "CN=IWAM_HYD-QA12,CN=Users,DC=hyd-qa,DC=com",
|
||||
"username": "IWAM_HYD-QA12",
|
||||
"domain": "CN=IWAM_HYD-QA12"
|
||||
},
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"principal": "CN=SUPPORT_388945a0,CN=Users,DC=hyd-qa,DC=com",
|
||||
"username": "SUPPORT_388945a0",
|
||||
"domain": "CN=SUPPORT_388945a0"
|
||||
},
|
||||
{
|
||||
"principal": "CN=jessica j,CN=Users,DC=hyd-qa,DC=com",
|
||||
"firstname": "jessica",
|
||||
"lastname": "j",
|
||||
"username": "jessica",
|
||||
"domain": "CN=jessica j"
|
||||
},
|
||||
{
|
||||
"principal": "CN=krbtgt,CN=Users,DC=hyd-qa,DC=com",
|
||||
"username": "krbtgt",
|
||||
"domain": "CN=krbtgt"
|
||||
},
|
||||
{
|
||||
"email": "sadhu@sadhu.com",
|
||||
"principal": "CN=sadhu,CN=Users,DC=hyd-qa,DC=com",
|
||||
"firstname": "sadhu",
|
||||
"username": "sadhu",
|
||||
"domain": "CN=sadhu"
|
||||
},
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"principal": "CN=sangee1 hariharan,CN=Users,DC=hyd-qa,DC=com",
|
||||
"firstname": "sangee1",
|
||||
"lastname": "hariharan",
|
||||
"username": "sangee1",
|
||||
"domain": "CN=sangee1 hariharan"
|
||||
},
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"principal": "CN=sanjeev n.,CN=Users,DC=hyd-qa,DC=com",
|
||||
"firstname": "sanjeev",
|
||||
"username": "sanjeev",
|
||||
"domain": "CN=sanjeev n."
|
||||
},
|
||||
{
|
||||
"email": "test@test.com",
|
||||
"principal": "CN=test1dddd,CN=Users,DC=hyd-qa,DC=com",
|
||||
"firstname": "test1",
|
||||
"username": "test1dddd",
|
||||
"domain": "CN=test1dddd"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
*/
|
||||
//for testing only (end)
|
||||
|
||||
$table.find('tr').remove();
|
||||
if (json.ldapuserresponse.count > 0) {
|
||||
$(json.ldapuserresponse.LdapUser).each(function() {
|
||||
var $result = $('<tr>');
|
||||
@ -228,7 +155,9 @@
|
||||
$('<td>').addClass('username').html(_s(this.username))
|
||||
.attr('title', this.username),
|
||||
$('<td>').addClass('email').html(_s(this.email))
|
||||
.attr('title', _s(this.email))
|
||||
.attr('title', _s(this.email)),
|
||||
$('<td>').addClass('email').html(_s(this.conflictingusersource))
|
||||
.attr('title', _s(this.conflictingusersource))
|
||||
)
|
||||
|
||||
$table.append($result);
|
||||
@ -243,14 +172,20 @@
|
||||
$table.append($result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}) };
|
||||
loadList();
|
||||
|
||||
} else {
|
||||
var informationWithinLdapFields = $.extend(true,{},args.informationWithinLdap);
|
||||
// we are not in ldap so
|
||||
delete informationWithinLdapFields.conflictingusersource;
|
||||
|
||||
var informationWithinLdap = cloudStack.dialog.createForm({
|
||||
context: context,
|
||||
noDialog: true,
|
||||
form: {
|
||||
title: '',
|
||||
fields: args.informationWithinLdap
|
||||
fields: informationWithinLdapFields
|
||||
}
|
||||
});
|
||||
|
||||
@ -267,13 +202,16 @@
|
||||
$wizard.removeClass('multi-wizard');
|
||||
}
|
||||
|
||||
var informationNotInLdap = $.extend(true,{},args.informationNotInLdap);
|
||||
|
||||
if (!ldapStatus) {
|
||||
delete args.informationNotInLdap.ldapGroupName;
|
||||
delete informationNotInLdap.filter;
|
||||
delete informationNotInLdap.ldapGroupName;
|
||||
}
|
||||
|
||||
if (g_idpList == null) {
|
||||
delete args.informationNotInLdap.samlEnable;
|
||||
delete args.informationNotInLdap.samlEntity;
|
||||
delete informationNotInLdap.samlEnable;
|
||||
delete informationNotInLdap.samlEntity;
|
||||
}
|
||||
|
||||
var informationNotInLdap = cloudStack.dialog.createForm({
|
||||
@ -281,12 +219,21 @@
|
||||
noDialog: true,
|
||||
form: {
|
||||
title: '',
|
||||
fields: args.informationNotInLdap
|
||||
fields: informationNotInLdap
|
||||
}
|
||||
});
|
||||
|
||||
var informationNotInLdapForm = informationNotInLdap.$formContainer.find('form .form-item');
|
||||
informationNotInLdapForm.find('.value #label_filterBy').addClass('required');
|
||||
informationNotInLdapForm.find('.value #label_filterBy').change(function() {
|
||||
userFilter = $wizard.find('#label_filterBy').val();
|
||||
loadList();
|
||||
});
|
||||
informationNotInLdapForm.find('.value #label_domain').addClass('required');
|
||||
informationNotInLdapForm.find('.value #label_domain').change(function() {
|
||||
domainId = $wizard.find('#label_domain').val();
|
||||
loadList();
|
||||
});
|
||||
informationNotInLdapForm.find('.value #label_type').addClass('required');
|
||||
if (!ldapStatus) {
|
||||
informationNotInLdapForm.css('background', 'none');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user