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:
dahn 2020-01-20 16:02:33 +01:00 committed by GitHub
parent 9b7acfde1e
commit 5ff932eb86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 3839 additions and 291 deletions

View File

@ -336,7 +336,10 @@ public class ApiConstants {
public static final String URL = "url"; public static final String URL = "url";
public static final String USAGE_INTERFACE = "usageinterface"; public static final String USAGE_INTERFACE = "usageinterface";
public static final String USER_DATA = "userdata"; 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_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 USE_SSL = "ssl";
public static final String USERNAME = "username"; public static final String USERNAME = "username";
public static final String USER_CONFIGURABLE = "userconfigurable"; public static final String USER_CONFIGURABLE = "userconfigurable";

View File

@ -111,6 +111,8 @@ public interface QueryService {
ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException; ListResponse<UserResponse> searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException;
ListResponse<UserResponse> searchForUsers(Long domainId, boolean recursive) throws PermissionDeniedException;
ListResponse<EventResponse> searchForEvents(ListEventsCmd cmd); ListResponse<EventResponse> searchForEvents(ListEventsCmd cmd);
ListResponse<ResourceTagResponse> listTags(ListTagsCmd cmd); ListResponse<ResourceTagResponse> listTags(ListTagsCmd cmd);

View File

@ -27,12 +27,23 @@
<version>4.14.0.0-SNAPSHOT</version> <version>4.14.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </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> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.codehaus.gmaven</groupId> <groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId> <artifactId>gmaven-plugin</artifactId>
<version>1.3</version> <version>${gmaven.version}</version>
<configuration> <configuration>
<providerSelection>1.7</providerSelection> <providerSelection>1.7</providerSelection>
</configuration> </configuration>
@ -58,7 +69,7 @@
<dependency> <dependency>
<groupId>org.codehaus.gmaven.runtime</groupId> <groupId>org.codehaus.gmaven.runtime</groupId>
<artifactId>gmaven-runtime-1.7</artifactId> <artifactId>gmaven-runtime-1.7</artifactId>
<version>1.3</version> <version>${gmaven.version}</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>org.codehaus.groovy</groupId> <groupId>org.codehaus.groovy</groupId>
@ -81,38 +92,126 @@
<include>**/*Spec.groovy</include> <include>**/*Spec.groovy</include>
<include>**/*Test.java</include> <include>**/*Test.java</include>
</includes> </includes>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>com.btmatthews.maven.plugins</groupId> <groupId>com.btmatthews.maven.plugins</groupId>
<artifactId>ldap-maven-plugin</artifactId> <artifactId>ldap-maven-plugin</artifactId>
<version>1.1.0</version> <version>${ldap-maven.version}</version>
<configuration> <configuration>
<monitorPort>11389</monitorPort> <monitorPort>11389</monitorPort>
<monitorKey>ldap</monitorKey> <monitorKey>ldap</monitorKey>
<daemon>false</daemon> <daemon>false</daemon>
<rootDn>dc=cloudstack,dc=org</rootDn> <rootDn>dc=cloudstack,dc=org</rootDn>
<ldapPort>10389</ldapPort> <ldapPort>10389</ldapPort>
<ldifFile>test/resources/cloudstack.org.ldif</ldifFile> <ldifFile>src/test/resources/cloudstack.org.ldif</ldifFile>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
<testSourceDirectory>test</testSourceDirectory> <testSourceDirectory>src/test/java</testSourceDirectory>
</build> </build>
<dependencies> <dependencies>
<!-- Mandatory dependencies for using Spock --> <!-- Mandatory dependencies for using Spock -->
<dependency>
<groupId>com.btmatthews.ldapunit</groupId>
<artifactId>ldapunit</artifactId>
<version>${ldapunit.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.spockframework</groupId> <groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId> <artifactId>spock-core</artifactId>
<version>1.1-groovy-2.4</version> <version>${groovy.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Optional dependencies for using Spock --> <!-- Optional dependencies for using Spock -->
<dependency> <!-- enables mocking of classes (in addition to interfaces) --> <dependency> <!-- enables mocking of classes (in addition to interfaces) -->
<groupId>cglib</groupId> <groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId> <artifactId>cglib-nodep</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </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> </dependencies>
</project> </project>

View File

@ -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";
}

View File

@ -16,18 +16,26 @@
// under the License. // under the License.
package org.apache.cloudstack.api.command; package org.apache.cloudstack.api.command;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject; 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.log4j.Logger;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.BaseListCmd; import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException; 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.LdapUserResponse;
import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserResponse;
@ -38,8 +46,37 @@ import org.apache.cloudstack.query.QueryService;
import com.cloud.user.Account; 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 class LdapListUsersCmd extends BaseListCmd {
public static final Logger s_logger = Logger.getLogger(LdapListUsersCmd.class.getName()); public static final Logger s_logger = Logger.getLogger(LdapListUsersCmd.class.getName());
@ -47,15 +84,29 @@ public class LdapListUsersCmd extends BaseListCmd {
@Inject @Inject
private LdapManager _ldapManager; private LdapManager _ldapManager;
@Inject
private QueryService _queryService;
@Parameter(name = "listtype", @Parameter(name = "listtype",
type = CommandType.STRING, type = CommandType.STRING,
required = false, required = false,
description = "Determines whether all ldap users are returned or just non-cloudstack users") 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; 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() { public LdapListUsersCmd() {
super(); super();
} }
@ -66,27 +117,35 @@ public class LdapListUsersCmd extends BaseListCmd {
_queryService = queryService; _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) { private List<LdapUserResponse> createLdapUserResponse(final List<LdapUser> users) {
final List<LdapUserResponse> ldapResponses = new ArrayList<LdapUserResponse>(); final List<LdapUserResponse> ldapResponses = new ArrayList<LdapUserResponse>();
for (final LdapUser user : users) { for (final LdapUser user : users) {
if (getListType().equals("all") || !isACloudstackUser(user)) { final LdapUserResponse ldapResponse = _ldapManager.createLdapUserResponse(user);
final LdapUserResponse ldapResponse = _ldapManager.createLdapUserResponse(user); ldapResponse.setObjectName("LdapUser");
ldapResponse.setObjectName("LdapUser"); ldapResponses.add(ldapResponse);
ldapResponses.add(ldapResponse);
}
} }
return ldapResponses; return ldapResponses;
} }
private List<UserResponse> cloudstackUsers = null;
@Override @Override
public void execute() throws ServerApiException { public void execute() throws ServerApiException {
List<LdapUserResponse> ldapResponses = null; cloudstackUsers = null;
List<LdapUserResponse> ldapResponses = new ArrayList<LdapUserResponse>();
final ListResponse<LdapUserResponse> response = new ListResponse<LdapUserResponse>(); final ListResponse<LdapUserResponse> response = new ListResponse<LdapUserResponse>();
try { try {
final List<LdapUser> users = _ldapManager.getUsers(null); final List<LdapUser> users = _ldapManager.getUsers(domainId);
ldapResponses = createLdapUserResponse(users); ldapResponses = createLdapUserResponse(users);
// now filter and annotate
ldapResponses = applyUserFilter(ldapResponses);
} catch (final NoLdapUserMatchingQueryException ex) { } catch (final NoLdapUserMatchingQueryException ex) {
ldapResponses = new ArrayList<LdapUserResponse>(); // ok, we'll make do with the empty list ldapResponses = new ArrayList<LdapUserResponse>();
} finally { } finally {
response.setResponses(ldapResponses); response.setResponses(ldapResponses);
response.setResponseName(getCommandName()); 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 @Override
public String getCommandName() { public String getCommandName() {
return s_name; return s_name;
@ -104,20 +200,306 @@ public class LdapListUsersCmd extends BaseListCmd {
return Account.ACCOUNT_ID_SYSTEM; return Account.ACCOUNT_ID_SYSTEM;
} }
private String getListType() { String getListTypeString() {
return listType == null ? "all" : listType; return listType == null ? "all" : listType;
} }
private boolean isACloudstackUser(final LdapUser ldapUser) { String getUserFilterString() {
final ListResponse<UserResponse> response = _queryService.searchForUsers(new ListUsersCmd()); return userFilter == null ? getListTypeString() == null ? "NoFilter" : getListTypeString().equals("all") ? "NoFilter" : "AnyDomain" : userFilter;
final List<UserResponse> cloudstackUsers = response.getResponses(); }
if (cloudstackUsers != null && cloudstackUsers.size() != 0) {
for (final UserResponse cloudstackUser : response.getResponses()) { 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 (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; 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; 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);
}
}
} }

View File

@ -18,35 +18,41 @@ package org.apache.cloudstack.api.response;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param; import com.cloud.serializer.Param;
import org.apache.cloudstack.api.LdapConstants;
public class LdapUserResponse extends BaseResponse { public class LdapUserResponse extends BaseResponse {
@SerializedName("email") @SerializedName(ApiConstants.EMAIL)
@Param(description = "The user's email") @Param(description = "The user's email")
private String email; private String email;
@SerializedName("principal") @SerializedName(LdapConstants.PRINCIPAL)
@Param(description = "The user's principle") @Param(description = "The user's principle")
private String principal; private String principal;
@SerializedName("firstname") @SerializedName(ApiConstants.FIRSTNAME)
@Param(description = "The user's firstname") @Param(description = "The user's firstname")
private String firstname; private String firstname;
@SerializedName("lastname") @SerializedName(ApiConstants.LASTNAME)
@Param(description = "The user's lastname") @Param(description = "The user's lastname")
private String lastname; private String lastname;
@SerializedName("username") @SerializedName(ApiConstants.USERNAME)
@Param(description = "The user's username") @Param(description = "The user's username")
private String username; private String username;
@SerializedName("domain") @SerializedName(ApiConstants.DOMAIN)
@Param(description = "The user's domain") @Param(description = "The user's domain")
private String 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() { public LdapUserResponse() {
super(); super();
} }
@ -61,6 +67,11 @@ public class LdapUserResponse extends BaseResponse {
this.domain = domain; 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() { public String getEmail() {
return email; return email;
} }
@ -85,6 +96,10 @@ public class LdapUserResponse extends BaseResponse {
return domain; return domain;
} }
public String getUserSource() {
return userSource;
}
public void setEmail(final String email) { public void setEmail(final String email) {
this.email = email; this.email = email;
} }
@ -108,4 +123,67 @@ public class LdapUserResponse extends BaseResponse {
public void setDomain(String domain) { public void setDomain(String domain) {
this.domain = 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();
}
} }

View File

@ -38,7 +38,7 @@ import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
public class LdapAuthenticator extends AdapterBase implements UserAuthenticator { 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 @Inject
private LdapManager _ldapManager; 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) { 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); 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 (LOGGER.isDebugEnabled()) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { LOGGER.debug("Retrieving ldap user: " + username);
s_logger.debug("Username or Password cannot be empty");
return rc;
} }
if (_ldapManager.isLdapEnabled()) { // TODO not allowing an empty password is a policy we shouldn't decide on. A private cloud may well want to allow this.
final UserAccount user = _userAccountDao.getUserAccount(username, domainId); if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
List<LdapTrustMapVO> ldapTrustMapVOs = _ldapManager.getDomainLinkage(domainId); if (_ldapManager.isLdapEnabled(domainId) || _ldapManager.isLdapEnabled()) {
if(ldapTrustMapVOs != null && ldapTrustMapVOs.size() > 0) { if (LOGGER.isTraceEnabled()) {
if(ldapTrustMapVOs.size() == 1 && ldapTrustMapVOs.get(0).getAccountId() == 0) { LOGGER.trace("LDAP is enabled in the ldapManager");
// We have a single mapping of a domain to an ldap group or ou }
return authenticate(username, password, domainId, user, ldapTrustMapVOs.get(0)); final UserAccount user = _userAccountDao.getUserAccount(username, domainId);
} else { if (user != null && ! User.Source.LDAP.equals(user.getSource())) {
// we are dealing with mapping of accounts in a domain to ldap groups return rc;
return authenticate(username, password, domainId, user, ldapTrustMapVOs); }
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; 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. * 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 * @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 * @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); Pair<Boolean, ActionOnFailedAuthentication> rc = new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
try { try {
LdapUser ldapUser = _ldapManager.getUser(username, domainId); LdapUser ldapUser = _ldapManager.getUser(username, domainId);
List<String> memberships = ldapUser.getMemberships(); List<String> memberships = ldapUser.getMemberships();
tracelist("memberships for " + username, memberships);
List<String> mappedGroups = getMappedGroups(ldapTrustMapVOs); List<String> mappedGroups = getMappedGroups(ldapTrustMapVOs);
tracelist("mappedgroups for " + username, mappedGroups);
mappedGroups.retainAll(memberships); mappedGroups.retainAll(memberships);
tracelist("actual groups for " + username, mappedGroups);
// check membership, there must be only one match in this domain // check membership, there must be only one match in this domain
if(ldapUser.isDisabled()) { if(ldapUser.isDisabled()) {
logAndDisable(userAccount, "attempt to log on using disabled ldap user " + userAccount.getUsername(), false); 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 // a valid ldap configured user exists
LdapTrustMapVO mapping = _ldapManager.getLinkedLdapGroup(domainId,mappedGroups.get(0)); LdapTrustMapVO mapping = _ldapManager.getLinkedLdapGroup(domainId,mappedGroups.get(0));
// we could now assert that ldapTrustMapVOs.contains(mapping); // we could now assert that ldapTrustMapVOs.contains(mapping);
// createUser in Account can only be done by account name not by account id // createUser in Account can only be done by account name not by account id;
String accountName = _accountManager.getAccount(mapping.getAccountId()).getAccountName(); 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)); 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 // for security reasons we keep processing on faulty login attempt to not give a way information on userid existence
if (userAccount == null) { if (userAccount == null) {
// new user that is in ldap; authenticate and create // new user that is in ldap; authenticate and create
@ -146,16 +175,29 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
} }
} }
} catch (NoLdapUserMatchingQueryException e) { } catch (NoLdapUserMatchingQueryException e) {
s_logger.debug(e.getMessage()); LOGGER.debug(e.getMessage());
disableUserInCloudStack(userAccount); disableUserInCloudStack(userAccount);
} }
return rc; 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) { private void logAndDisable(UserAccount userAccount, String msg, boolean remove) {
if (s_logger.isInfoEnabled()) { if (LOGGER.isInfoEnabled()) {
s_logger.info(msg); LOGGER.info(msg);
} }
if(remove) { if(remove) {
removeUserInCloudStack(userAccount); 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<>(); List<String> groups = new ArrayList<>();
for (LdapTrustMapVO vo : ldapTrustMapVOs) { for (LdapTrustMapVO vo : ldapTrustMapVOs) {
groups.add(vo.getName()); groups.add(vo.getName());
@ -188,7 +230,9 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
final short accountType = ldapTrustMapVO.getAccountType(); final short accountType = ldapTrustMapVO.getAccountType();
processLdapUser(password, domainId, user, rc, ldapUser, accountType); processLdapUser(password, domainId, user, rc, ldapUser, accountType);
} catch (NoLdapUserMatchingQueryException e) { } catch (NoLdapUserMatchingQueryException e) {
s_logger.debug(e.getMessage()); LOGGER.debug(e.getMessage());
// no user in ldap ==>> disable user in cloudstack
disableUserInCloudStack(user);
} }
return rc; return rc;
} }
@ -229,12 +273,16 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
if(!ldapUser.isDisabled()) { if(!ldapUser.isDisabled()) {
result = _ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId); result = _ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId);
} else { } 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) { } 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) ? return (!result && user != null) ?
new Pair<Boolean, ActionOnFailedAuthentication>(result, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT): new Pair<Boolean, ActionOnFailedAuthentication>(result, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT):
new Pair<Boolean, ActionOnFailedAuthentication>(result, null); new Pair<Boolean, ActionOnFailedAuthentication>(result, null);

View File

@ -38,7 +38,6 @@ public interface LdapManager extends PluggableService {
LdapConfigurationResponse addConfiguration(final LdapAddConfigurationCmd cmd) throws InvalidParameterValueException; LdapConfigurationResponse addConfiguration(final LdapAddConfigurationCmd cmd) throws InvalidParameterValueException;
@Deprecated
LdapConfigurationResponse addConfiguration(String hostname, int port, Long domainId) throws InvalidParameterValueException; LdapConfigurationResponse addConfiguration(String hostname, int port, Long domainId) throws InvalidParameterValueException;
boolean canAuthenticate(String principal, String password, final Long domainId); boolean canAuthenticate(String principal, String password, final Long domainId);
@ -62,6 +61,8 @@ public interface LdapManager extends PluggableService {
boolean isLdapEnabled(); boolean isLdapEnabled();
boolean isLdapEnabled(long domainId);
Pair<List<? extends LdapConfigurationVO>, Integer> listConfigurations(LdapListConfigurationCmd cmd); Pair<List<? extends LdapConfigurationVO>, Integer> listConfigurations(LdapListConfigurationCmd cmd);
List<LdapUser> searchUsers(String query) throws NoLdapUserMatchingQueryException; List<LdapUser> searchUsers(String query) throws NoLdapUserMatchingQueryException;

View File

@ -25,6 +25,7 @@ import javax.naming.NamingException;
import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapContext;
import java.util.UUID; import java.util.UUID;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.LdapValidator; import org.apache.cloudstack.api.LdapValidator;
import org.apache.cloudstack.api.command.LDAPConfigCmd; import org.apache.cloudstack.api.command.LDAPConfigCmd;
import org.apache.cloudstack.api.command.LDAPRemoveCmd; import org.apache.cloudstack.api.command.LDAPRemoveCmd;
@ -57,7 +58,7 @@ import com.cloud.utils.Pair;
@Component @Component
public class LdapManagerImpl implements LdapManager, LdapValidator { 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 @Inject
private LdapConfigurationDao _ldapConfigurationDao; private LdapConfigurationDao _ldapConfigurationDao;
@ -79,14 +80,13 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
@Inject @Inject
LdapTrustMapDao _ldapTrustMapDao; LdapTrustMapDao _ldapTrustMapDao;
public LdapManagerImpl() { public LdapManagerImpl() {
super(); super();
} }
public LdapManagerImpl(final LdapConfigurationDao ldapConfigurationDao, final LdapContextFactory ldapContextFactory, final LdapUserManagerFactory ldapUserManagerFactory, public LdapManagerImpl(final LdapConfigurationDao ldapConfigurationDao, final LdapContextFactory ldapContextFactory, final LdapUserManagerFactory ldapUserManagerFactory,
final LdapConfiguration ldapConfiguration) { final LdapConfiguration ldapConfiguration) {
super(); this();
_ldapConfigurationDao = ldapConfigurationDao; _ldapConfigurationDao = ldapConfigurationDao;
_ldapContextFactory = ldapContextFactory; _ldapContextFactory = ldapContextFactory;
_ldapUserManagerFactory = ldapUserManagerFactory; _ldapUserManagerFactory = ldapUserManagerFactory;
@ -118,10 +118,10 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
context = _ldapContextFactory.createBindContext(providerUrl,domainId); context = _ldapContextFactory.createBindContext(providerUrl,domainId);
configuration = new LdapConfigurationVO(hostname, port, domainId); configuration = new LdapConfigurationVO(hostname, port, domainId);
_ldapConfigurationDao.persist(configuration); _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); return createLdapConfigurationResponse(configuration);
} catch (NamingException | IOException e) { } 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"); throw new InvalidParameterValueException("Unable to bind to the given LDAP server");
} finally { } finally {
closeContext(context); closeContext(context);
@ -142,12 +142,15 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
public boolean canAuthenticate(final String principal, final String password, final Long domainId) { public boolean canAuthenticate(final String principal, final String password, final Long domainId) {
try { try {
// TODO return the right account for this user // 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); closeContext(context);
if(LOGGER.isTraceEnabled()) {
LOGGER.trace(String.format("User(%s) authenticated for domain(%s)", principal, domainId));
}
return true; return true;
} catch (NamingException | IOException e) { } catch (NamingException | IOException e) {/* AuthenticationException is caught as NamingException */
s_logger.debug("Exception while doing an LDAP bind for user "+" "+principal, e); LOGGER.debug("Exception while doing an LDAP bind for user "+" "+principal, e);
s_logger.info("Failed to authenticate user: " + principal + ". incorrect password."); LOGGER.info("Failed to authenticate user: " + principal + ". incorrect password.");
return false; return false;
} }
} }
@ -158,7 +161,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
context.close(); context.close();
} }
} catch (final NamingException e) { } 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) { public LdapConfigurationResponse createLdapConfigurationResponse(final LdapConfigurationVO configuration) {
String domainUuid = null; String domainUuid = null;
if(configuration.getDomainId() != 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); 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); throw new InvalidParameterValueException("Cannot find configuration with hostname " + hostname);
} else { } else {
_ldapConfigurationDao.remove(configuration.getId()); _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); return createLdapConfigurationResponse(configuration);
} }
} }
@ -231,7 +237,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUser(escapedUsername, context, domainId); return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUser(escapedUsername, context, domainId);
} catch (NamingException | IOException e) { } 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); throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username);
} finally { } finally {
closeContext(context); closeContext(context);
@ -244,9 +250,15 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
try { try {
context = _ldapContextFactory.createBindContext(domainId); context = _ldapContextFactory.createBindContext(domainId);
final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username); 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) { } 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); throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username + " in group: " + name + " of type: " + type);
} finally { } finally {
closeContext(context); closeContext(context);
@ -260,7 +272,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
context = _ldapContextFactory.createBindContext(domainId); context = _ldapContextFactory.createBindContext(domainId);
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsers(context, domainId); return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsers(context, domainId);
} catch (NamingException | IOException e) { } catch (NamingException | IOException e) {
s_logger.debug("ldap Exception: ",e); LOGGER.debug("ldap Exception: ",e);
throw new NoLdapUserMatchingQueryException("*"); throw new NoLdapUserMatchingQueryException("*");
} finally { } finally {
closeContext(context); closeContext(context);
@ -274,7 +286,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
context = _ldapContextFactory.createBindContext(domainId); context = _ldapContextFactory.createBindContext(domainId);
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsersInGroup(groupName, context, domainId); return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsersInGroup(groupName, context, domainId);
} catch (NamingException | IOException e) { } catch (NamingException | IOException e) {
s_logger.debug("ldap NamingException: ",e); LOGGER.debug("ldap NamingException: ",e);
throw new NoLdapUserMatchingQueryException("groupName=" + groupName); throw new NoLdapUserMatchingQueryException("groupName=" + groupName);
} finally { } finally {
closeContext(context); closeContext(context);
@ -286,6 +298,13 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
return listConfigurations(new LdapListConfigurationCmd(this)).second() > 0; 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 @Override
public Pair<List<? extends LdapConfigurationVO>, Integer> listConfigurations(final LdapListConfigurationCmd cmd) { public Pair<List<? extends LdapConfigurationVO>, Integer> listConfigurations(final LdapListConfigurationCmd cmd) {
final String hostname = cmd.getHostname(); final String hostname = cmd.getHostname();
@ -304,7 +323,7 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username); final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username);
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUsers("*" + escapedUsername + "*", context, null); return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUsers("*" + escapedUsername + "*", context, null);
} catch (NamingException | IOException e) { } catch (NamingException | IOException e) {
s_logger.debug("ldap Exception: ",e); LOGGER.debug("ldap Exception: ",e);
throw new NoLdapUserMatchingQueryException(username); throw new NoLdapUserMatchingQueryException(username);
} finally { } finally {
closeContext(context); closeContext(context);
@ -313,9 +332,13 @@ public class LdapManagerImpl implements LdapManager, LdapValidator {
@Override @Override
public LinkDomainToLdapResponse linkDomainToLdap(LinkDomainToLdapCmd cmd) { public LinkDomainToLdapResponse linkDomainToLdap(LinkDomainToLdapCmd cmd) {
Validate.isTrue(_ldapConfiguration.getBaseDn(cmd.getDomainId()) == null, "can not link a domain unless a basedn is configured for it."); final Long domainId = cmd.getDomainId();
Validate.notEmpty(cmd.getLdapDomain(), "ldapDomain cannot be empty, please supply a GROUP or OU name"); final String baseDn = _ldapConfiguration.getBaseDn(domainId);
return linkDomainToLdap(cmd.getDomainId(),cmd.getType(),cmd.getLdapDomain(),cmd.getAccountType()); 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) { 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()); DomainVO domain = domainDao.findById(vo.getDomainId());
String domainUuid = "<unknown>"; String domainUuid = "<unknown>";
if (domain == null) { 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 { } else {
domainUuid = domain.getUuid(); 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()); account = new AccountVO(cmd.getAccountName(), cmd.getDomainId(), null, cmd.getAccountType(), UUID.randomUUID().toString());
accountDao.persist((AccountVO)account); accountDao.persist((AccountVO)account);
} }
Long accountId = account.getAccountId(); Long accountId = account.getAccountId();
clearOldAccountMapping(cmd);
LdapTrustMapVO vo = _ldapTrustMapDao.persist(new LdapTrustMapVO(cmd.getDomainId(), linkType, cmd.getLdapDomain(), cmd.getAccountType(), accountId)); LdapTrustMapVO vo = _ldapTrustMapDao.persist(new LdapTrustMapVO(cmd.getDomainId(), linkType, cmd.getLdapDomain(), cmd.getAccountType(), accountId));
DomainVO domain = domainDao.findById(vo.getDomainId()); DomainVO domain = domainDao.findById(vo.getDomainId());
String domainUuid = "<unknown>"; String domainUuid = "<unknown>";
if (domain == null) { 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 { } else {
domainUuid = domain.getUuid(); 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()); LinkAccountToLdapResponse response = new LinkAccountToLdapResponse(domainUuid, vo.getType().toString(), vo.getName(), vo.getAccountType(), account.getUuid(), cmd.getAccountName());
return response; 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);
}
}
}
} }

View File

@ -32,7 +32,7 @@ public class LdapUserManagerFactory implements ApplicationContextAware {
public static final Logger s_logger = Logger.getLogger(LdapUserManagerFactory.class.getName()); 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; private ApplicationContext applicationCtx;

View File

@ -33,16 +33,20 @@ import javax.naming.ldap.LdapContext;
import javax.naming.ldap.PagedResultsControl; import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl; import javax.naming.ldap.PagedResultsResponseControl;
import org.apache.cloudstack.ldap.dao.LdapTrustMapDao;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
public class OpenLdapUserManagerImpl implements LdapUserManager { 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 @Inject
protected LdapConfiguration _ldapConfiguration; protected LdapConfiguration _ldapConfiguration;
@Inject
LdapTrustMapDao _ldapTrustMapDao;
public OpenLdapUserManagerImpl() { public OpenLdapUserManagerImpl() {
} }
@ -82,25 +86,62 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
usernameFilter.append((username == null ? "*" : username)); usernameFilter.append((username == null ? "*" : username));
usernameFilter.append(")"); usernameFilter.append(")");
final StringBuilder memberOfFilter = new StringBuilder(); String memberOfAttribute = _ldapConfiguration.getUserMemberOfAttribute(domainId);
if (_ldapConfiguration.getSearchGroupPrinciple(domainId) != null) { StringBuilder ldapGroupsFilter = new StringBuilder();
if(s_logger.isDebugEnabled()) { // this should get the trustmaps for this domain
s_logger.debug("adding search filter for '" + _ldapConfiguration.getSearchGroupPrinciple(domainId) + List<String> ldapGroups = getMappedLdapGroups(domainId);
"', using " + _ldapConfiguration.getUserMemberOfAttribute(domainId)); if (null != ldapGroups && ldapGroups.size() > 0) {
ldapGroupsFilter.append("(|");
for (String ldapGroup : ldapGroups) {
ldapGroupsFilter.append(getMemberOfGroupString(ldapGroup, memberOfAttribute));
} }
memberOfFilter.append("(" + _ldapConfiguration.getUserMemberOfAttribute(domainId) + "="); ldapGroupsFilter.append(')');
memberOfFilter.append(_ldapConfiguration.getSearchGroupPrinciple(domainId)); }
memberOfFilter.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(); final StringBuilder result = new StringBuilder();
result.append("(&"); result.append("(&");
result.append(userObjectFilter); result.append(userObjectFilter);
result.append(usernameFilter); result.append(usernameFilter);
result.append(memberOfFilter); result.append(ldapGroupsFilter);
result.append(principleGroupFilter);
result.append(")"); 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) { private String generateGroupSearchFilter(final String groupName, Long domainId) {
@ -212,7 +253,7 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
try{ try{
users.add(getUserForDn(userdn, context, domainId)); users.add(getUserForDn(userdn, context, domainId));
} catch (NamingException e){ } 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)); searchControls.setReturningAttributes(_ldapConfiguration.getReturnAttributes(domainId));
NamingEnumeration<SearchResult> results = context.search(basedn, searchString, searchControls); NamingEnumeration<SearchResult> results = context.search(basedn, searchString, searchControls);
if(s_logger.isDebugEnabled()) { if(LOGGER.isDebugEnabled()) {
s_logger.debug("searching user(s) with filter: \"" + searchString + "\""); LOGGER.debug("searching user(s) with filter: \"" + searchString + "\"");
} }
final List<LdapUser> users = new ArrayList<LdapUser>(); final List<LdapUser> users = new ArrayList<LdapUser>();
while (results.hasMoreElements()) { while (results.hasMoreElements()) {
@ -277,7 +318,7 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
String basedn = _ldapConfiguration.getBaseDn(domainId); String basedn = _ldapConfiguration.getBaseDn(domainId);
if (StringUtils.isBlank(basedn)) { 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; byte[] cookie = null;
int pageSize = _ldapConfiguration.getLdapPageSize(domainId); int pageSize = _ldapConfiguration.getLdapPageSize(domainId);
@ -301,7 +342,7 @@ public class OpenLdapUserManagerImpl implements LdapUserManager {
} }
} }
} else { } 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)}); context.setRequestControls(new Control[] {new PagedResultsControl(pageSize, cookie, Control.CRITICAL)});
} while (cookie != null); } while (cookie != null);

View File

@ -75,7 +75,7 @@ public class LdapConfigurationDaoImpl extends GenericDaoBase<LdapConfigurationVO
private SearchCriteria<LdapConfigurationVO> getSearchCriteria(String hostname, int port, Long domainId) { private SearchCriteria<LdapConfigurationVO> getSearchCriteria(String hostname, int port, Long domainId) {
SearchCriteria<LdapConfigurationVO> sc; SearchCriteria<LdapConfigurationVO> sc;
if (domainId == null) { if (domainId == null) {
sc = listDomainConfigurationsSearch.create(); sc = listGlobalConfigurationsSearch.create();
} else { } else {
sc = listDomainConfigurationsSearch.create(); sc = listDomainConfigurationsSearch.create();
sc.setParameters("domain_id", domainId); sc.setParameters("domain_id", domainId);

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -18,6 +18,9 @@ package org.apache.cloudstack.ldap;
import com.cloud.server.auth.UserAuthenticator; 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.UserAccount;
import com.cloud.user.UserAccountVO; import com.cloud.user.UserAccountVO;
import com.cloud.user.dao.UserAccountDao; import com.cloud.user.dao.UserAccountDao;
@ -25,13 +28,20 @@ import com.cloud.utils.Pair;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -43,9 +53,12 @@ public class LdapAuthenticatorTest {
@Mock @Mock
UserAccountDao userAccountDao; UserAccountDao userAccountDao;
@Mock @Mock
AccountManager accountManager;
@Mock
UserAccount user = new UserAccountVO(); UserAccount user = new UserAccountVO();
LdapAuthenticator ldapAuthenticator; @InjectMocks
LdapAuthenticator ldapAuthenticator = new LdapAuthenticator();
private String username = "bbanner"; private String username = "bbanner";
private String principal = "cd=bbanner"; private String principal = "cd=bbanner";
private String hardcoded = "password"; private String hardcoded = "password";
@ -53,7 +66,18 @@ public class LdapAuthenticatorTest {
@Before @Before
public void setUp() throws Exception { 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 @Test
@ -62,9 +86,39 @@ public class LdapAuthenticatorTest {
Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> rc; Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> rc;
when(ldapManager.getUser(username, domainId)).thenReturn(ldapUser); when(ldapManager.getUser(username, domainId)).thenReturn(ldapUser);
rc = ldapAuthenticator.authenticate(username, "password", domainId, user); 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()); 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 @Test
public void authenticate() throws Exception { public void authenticate() throws Exception {
LdapUser ldapUser = new LdapUser(username, "a@b", "b", "banner", principal, "", false, null); LdapUser ldapUser = new LdapUser(username, "a@b", "b", "banner", principal, "", false, null);

View File

@ -16,7 +16,6 @@
// under the License. // under the License.
package org.apache.cloudstack.ldap; 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.LdapConfigurationDao;
import org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl; import org.apache.cloudstack.ldap.dao.LdapConfigurationDaoImpl;
import org.junit.Before; import org.junit.Before;
@ -24,118 +23,98 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner; 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.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class LdapConfigurationTest { public class LdapConfigurationTest {
private final LdapTestConfigTool ldapTestConfigTool = new LdapTestConfigTool();
LdapConfigurationDao ldapConfigurationDao; LdapConfigurationDao ldapConfigurationDao;
LdapConfiguration ldapConfiguration; LdapConfiguration ldapConfiguration;
private void overrideConfigValue(final String configKeyName, final Object o) throws IllegalAccessException, NoSuchFieldException { private void overrideConfigValue(LdapConfiguration ldapConfiguration, final String configKeyName, final Object o) throws IllegalAccessException, NoSuchFieldException
Field configKey = LdapConfiguration.class.getDeclaredField(configKeyName); {
configKey.setAccessible(true); ldapTestConfigTool.overrideConfigValue(ldapConfiguration, configKeyName, o);
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);
} }
@Before @Before public void init() throws Exception {
public void init() throws Exception { ldapConfigurationDao = new LdapConfigurationDaoImpl();
ldapConfigurationDao = new LdapConfigurationDaoImpl(); ldapConfiguration = new LdapConfiguration(ldapConfigurationDao);
ldapConfiguration = new LdapConfiguration(ldapConfigurationDao);; ;
} }
@Test @Test public void getAuthenticationReturnsSimple() throws Exception {
public void getAuthenticationReturnsSimple() throws Exception { ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapBindPrincipal", "cn=bla");
overrideConfigValue("ldapBindPrincipal", "cn=bla"); ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapBindPassword", "pw");
overrideConfigValue("ldapBindPassword", "pw");
String authentication = ldapConfiguration.getAuthentication(null); String authentication = ldapConfiguration.getAuthentication(null);
assertEquals("authentication should be set to simple", "simple", authentication); assertEquals("authentication should be set to simple", "simple", authentication);
} }
@Test public void getBaseDnReturnsABaseDn() throws Exception {
@Test ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapBaseDn", "dc=cloudstack,dc=org");
public void getBaseDnReturnsABaseDn() throws Exception {
overrideConfigValue("ldapBaseDn", "dc=cloudstack,dc=org");
String baseDn = ldapConfiguration.getBaseDn(null); 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 @Test public void getGroupUniqueMemberAttribute() throws Exception {
public void getGroupUniqueMemberAttribute() throws Exception { String[] groupNames = {"bla", "uniquemember", "memberuid", "", null};
String [] groupNames = {"bla", "uniquemember", "memberuid", "", null}; for (String groupObject : groupNames) {
for (String groupObject: groupNames) { ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapGroupUniqueMemberAttribute", groupObject);
overrideConfigValue("ldapGroupUniqueMemberAttribute", groupObject);
String expectedResult = null; String expectedResult = null;
if(groupObject == null) { if (groupObject == null) {
expectedResult = "uniquemember"; expectedResult = "uniquemember";
} else { } else {
expectedResult = groupObject; expectedResult = groupObject;
}; }
;
String result = ldapConfiguration.getGroupUniqueMemberAttribute(null); String result = ldapConfiguration.getGroupUniqueMemberAttribute(null);
assertEquals("testing for " + groupObject, expectedResult, result); assertEquals("testing for " + groupObject, expectedResult, result);
} }
} }
@Test @Test public void getSSLStatusCanBeTrue() throws Exception {
public void getSSLStatusCanBeTrue() throws Exception {
// given: "We have a ConfigDao with values for truststore and truststore password set" // given: "We have a ConfigDao with values for truststore and truststore password set"
overrideConfigValue("ldapTrustStore", "/tmp/ldap.ts"); ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapTrustStore", "/tmp/ldap.ts");
overrideConfigValue("ldapTrustStorePassword", "password"); ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapTrustStorePassword", "password");
assertTrue("A request is made to get the status of SSL should result in true", ldapConfiguration.getSSLStatus()); 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 // 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); 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 @Test public void getTrustStorePasswordResopnds() throws Exception {
public void getTrustStorePasswordResopnds() throws Exception {
// We have a ConfigDao with a value for truststore password // We have a ConfigDao with a value for truststore password
overrideConfigValue("ldapTrustStorePassword", "password"); ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapTrustStorePassword", "password");
String result = ldapConfiguration.getTrustStorePassword(); String result = ldapConfiguration.getTrustStorePassword();
assertEquals("The result is password", "password", result); assertEquals("The result is password", "password", result);
} }
@Test public void getGroupObject() throws Exception {
@Test String[] groupNames = {"bla", "groupOfUniqueNames", "groupOfNames", "", null};
public void getGroupObject() throws Exception { for (String groupObject : groupNames) {
String [] groupNames = {"bla", "groupOfUniqueNames", "groupOfNames", "", null}; ldapTestConfigTool.overrideConfigValue(ldapConfiguration, "ldapGroupObject", groupObject);
for (String groupObject: groupNames) {
overrideConfigValue("ldapGroupObject", groupObject);
String expectedResult = null; String expectedResult = null;
if(groupObject == null) { if (groupObject == null) {
expectedResult = "groupOfUniqueNames"; expectedResult = "groupOfUniqueNames";
} else { } else {
expectedResult = groupObject; expectedResult = groupObject;
}; }
;
String result = ldapConfiguration.getGroupObject(null); String result = ldapConfiguration.getGroupObject(null);
assertEquals("testing for " + groupObject, expectedResult, result); assertEquals("testing for " + groupObject, expectedResult, result);
} }
} }
@Test public void getNullLdapProvider() {
assertEquals(LdapUserManager.Provider.OPENLDAP, ldapConfiguration.getLdapProvider(null));
}
} }

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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

View 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>

View 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

View File

@ -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>

View File

@ -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=

View File

@ -413,6 +413,36 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
return response; 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 { private Pair<List<UserAccountJoinVO>, Integer> searchForUsersInternal(ListUsersCmd cmd) throws PermissionDeniedException {
Account caller = CallContext.current().getCallingAccount(); Account caller = CallContext.current().getCallingAccount();
@ -427,42 +457,52 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
} }
id = currentId; 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 username = cmd.getUsername();
Object type = cmd.getAccountType(); Object type = cmd.getAccountType();
Object accountName = cmd.getAccountName(); String accountName = cmd.getAccountName();
Object state = cmd.getState(); Object state = cmd.getState();
Object keyword = cmd.getKeyword(); 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(); SearchBuilder<UserAccountJoinVO> sb = _userAccountJoinDao.createSearchBuilder();
_accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); _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) { if (id != null && id == 1) {
// system user should NOT be searchable // system user should NOT be searchable
List<UserAccountJoinVO> emptyList = new ArrayList<UserAccountJoinVO>(); List<UserAccountJoinVO> emptyList = new ArrayList<UserAccountJoinVO>();
return new Pair<List<UserAccountJoinVO>, Integer>(emptyList, 0); return new Pair<List<UserAccountJoinVO>, Integer>(emptyList, 0);
} else if (id != null) { } else if (id != null) {
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); sb.and("id", sb.entity().getId(), Op.EQ);
} else { } else {
// this condition is used to exclude system user from the search // this condition is used to exclude system user from the search
// results // 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("type", sb.entity().getAccountType(), Op.EQ);
sb.and("domainId", sb.entity().getDomainId(), SearchCriteria.Op.EQ); sb.and("domainId", sb.entity().getDomainId(), Op.EQ);
sb.and("accountName", sb.entity().getAccountName(), SearchCriteria.Op.EQ); sb.and("accountName", sb.entity().getAccountName(), Op.EQ);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); sb.and("state", sb.entity().getState(), Op.EQ);
if ((accountName == null) && (domainId != null)) { 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(); SearchCriteria<UserAccountJoinVO> sc = sb.create();
@ -472,15 +512,15 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
if (keyword != null) { if (keyword != null) {
SearchCriteria<UserAccountJoinVO> ssc = _userAccountJoinDao.createSearchCriteria(); SearchCriteria<UserAccountJoinVO> ssc = _userAccountJoinDao.createSearchCriteria();
ssc.addOr("username", SearchCriteria.Op.LIKE, "%" + keyword + "%"); ssc.addOr("username", Op.LIKE, "%" + keyword + "%");
ssc.addOr("firstname", SearchCriteria.Op.LIKE, "%" + keyword + "%"); ssc.addOr("firstname", Op.LIKE, "%" + keyword + "%");
ssc.addOr("lastname", SearchCriteria.Op.LIKE, "%" + keyword + "%"); ssc.addOr("lastname", Op.LIKE, "%" + keyword + "%");
ssc.addOr("email", SearchCriteria.Op.LIKE, "%" + keyword + "%"); ssc.addOr("email", Op.LIKE, "%" + keyword + "%");
ssc.addOr("state", SearchCriteria.Op.LIKE, "%" + keyword + "%"); ssc.addOr("state", Op.LIKE, "%" + keyword + "%");
ssc.addOr("accountName", SearchCriteria.Op.LIKE, "%" + keyword + "%"); ssc.addOr("accountName", Op.LIKE, "%" + keyword + "%");
ssc.addOr("accountType", SearchCriteria.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) { if (username != null) {

View 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
}
],
}

View 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

View File

@ -181,7 +181,7 @@ class Account:
self.__dict__.update(items) self.__dict__.update(items)
@classmethod @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""" """Creates an account"""
cmd = createAccount.createAccountCmd() cmd = createAccount.createAccountCmd()
@ -213,6 +213,9 @@ class Account:
if roleid: if roleid:
cmd.roleid = roleid cmd.roleid = roleid
if account:
cmd.account = account
account = apiclient.createAccount(cmd) account = apiclient.createAccount(cmd)
return Account(account.__dict__) return Account(account.__dict__)

View File

@ -639,6 +639,7 @@
<th><translate key="label.name"/></th> <th><translate key="label.name"/></th>
<th><translate key="label.username"/></th> <th><translate key="label.username"/></th>
<th><translate key="label.email"/></th> <th><translate key="label.email"/></th>
<th><translate key="label.user.conflict"/></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -1759,8 +1759,10 @@ var dictionary = {
"label.use.vm.ips":"Use VM IPs", "label.use.vm.ips":"Use VM IPs",
"label.used":"Used", "label.used":"Used",
"label.user":"User", "label.user":"User",
"label.user.conflict":"Conflict",
"label.user.data":"User Data", "label.user.data":"User Data",
"label.user.details":"User details", "label.user.details":"User details",
"label.user.source":"source",
"label.user.vm":"User VM", "label.user.vm":"User VM",
"label.username":"Username", "label.username":"Username",
"label.username.lower":"username", "label.username.lower":"username",

View File

@ -68,10 +68,43 @@
required: true required: true
}, },
docID: 'helpAccountLastName' docID: 'helpAccountLastName'
} },
conflictingusersource: {
label: 'label.user.conflict',
validation: {
required: true
},
docID: 'helpConflictSource'
}
}, },
informationNotInLdap: { 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: { domainid: {
label: 'label.domain', label: 'label.domain',
docID: 'helpAccountDomain', docID: 'helpAccountDomain',

View File

@ -110,6 +110,11 @@ cloudStack.docs = {
}, },
//Ldap //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: { 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)', 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: { helpIPReservationNetworkCidr: {
desc: 'The CIDR of the entire network when IP reservation is configured', desc: 'The CIDR of the entire network when IP reservation is configured',
externalLink: ' ' externalLink: ''
}, },

View File

@ -111,6 +111,14 @@
}); });
if (ldapStatus) { 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'); var $table = $wizard.find('.ldap-account-choice tbody');
$("#label_ldap_group_name").on("keypress", function(event) { $("#label_ldap_group_name").on("keypress", function(event) {
if ($table.find("#tr-groupname-message").length === 0) { if ($table.find("#tr-groupname-message").length === 0) {
@ -125,94 +133,13 @@
$table.find("#tr-groupname-message").hide(); $table.find("#tr-groupname-message").hide();
} }
}); });
$.ajax({ loadList = function() { $.ajax({
url: createURL("listLdapUsers&listtype=new"), url: createURL("listLdapUsers&listtype=new&domainid=" + domainId + "&userfilter=" + userFilter),
dataType: "json", dataType: "json",
async: false, async: false,
success: function(json) { 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) { if (json.ldapuserresponse.count > 0) {
$(json.ldapuserresponse.LdapUser).each(function() { $(json.ldapuserresponse.LdapUser).each(function() {
var $result = $('<tr>'); var $result = $('<tr>');
@ -228,7 +155,9 @@
$('<td>').addClass('username').html(_s(this.username)) $('<td>').addClass('username').html(_s(this.username))
.attr('title', this.username), .attr('title', this.username),
$('<td>').addClass('email').html(_s(this.email)) $('<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); $table.append($result);
@ -243,14 +172,20 @@
$table.append($result); $table.append($result);
} }
} }
}); }) };
loadList();
} else { } else {
var informationWithinLdapFields = $.extend(true,{},args.informationWithinLdap);
// we are not in ldap so
delete informationWithinLdapFields.conflictingusersource;
var informationWithinLdap = cloudStack.dialog.createForm({ var informationWithinLdap = cloudStack.dialog.createForm({
context: context, context: context,
noDialog: true, noDialog: true,
form: { form: {
title: '', title: '',
fields: args.informationWithinLdap fields: informationWithinLdapFields
} }
}); });
@ -267,13 +202,16 @@
$wizard.removeClass('multi-wizard'); $wizard.removeClass('multi-wizard');
} }
var informationNotInLdap = $.extend(true,{},args.informationNotInLdap);
if (!ldapStatus) { if (!ldapStatus) {
delete args.informationNotInLdap.ldapGroupName; delete informationNotInLdap.filter;
delete informationNotInLdap.ldapGroupName;
} }
if (g_idpList == null) { if (g_idpList == null) {
delete args.informationNotInLdap.samlEnable; delete informationNotInLdap.samlEnable;
delete args.informationNotInLdap.samlEntity; delete informationNotInLdap.samlEntity;
} }
var informationNotInLdap = cloudStack.dialog.createForm({ var informationNotInLdap = cloudStack.dialog.createForm({
@ -281,12 +219,21 @@
noDialog: true, noDialog: true,
form: { form: {
title: '', title: '',
fields: args.informationNotInLdap fields: informationNotInLdap
} }
}); });
var informationNotInLdapForm = informationNotInLdap.$formContainer.find('form .form-item'); 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').addClass('required');
informationNotInLdapForm.find('.value #label_domain').change(function() {
domainId = $wizard.find('#label_domain').val();
loadList();
});
informationNotInLdapForm.find('.value #label_type').addClass('required'); informationNotInLdapForm.find('.value #label_type').addClass('required');
if (!ldapStatus) { if (!ldapStatus) {
informationNotInLdapForm.css('background', 'none'); informationNotInLdapForm.css('background', 'none');