/*
* ====================
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License("CDDL") (the "License"). You may not use this file
* except in compliance with the License.
*
* You can obtain a copy of the License at
* http://opensource.org/licenses/cddl1.php
* See the License for the specific language governing permissions and limitations
* under the License.
*
* When distributing the Covered Code, include this CDDL Header Notice in each file
* and include the License file at http://opensource.org/licenses/cddl1.php.
* If applicable, add the following below this CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
* ====================
*/
package org.identityconnectors.solaris;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.util.List;
import java.util.Set;
import org.identityconnectors.common.CollectionUtil;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.api.ConnectorFacade;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
import org.identityconnectors.solaris.attr.AccountAttribute;
import org.identityconnectors.solaris.attr.GroupAttribute;
import org.identityconnectors.solaris.test.SolarisTestBase;
import org.identityconnectors.solaris.test.SolarisTestCommon;
import org.identityconnectors.test.common.ToListResultsHandler;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SolarisConnectorTest extends SolarisTestBase {
private static final Log logger = Log.getLog(SolarisConnectorTest.class);
@Test
public void testBasicTest() {
String username = getUsername();
// positive test update shell:
Attribute expectedAttribute =
AttributeBuilder.build(AccountAttribute.SHELL.getName(), "/bin/sh");
Set<Attribute> replaceAttrs = CollectionUtil.newSet(expectedAttribute);
getFacade().update(ObjectClass.ACCOUNT, new Uid(getUsername()), replaceAttrs, null);
assertTrue(checkUser(username, expectedAttribute));
// negative test update shell:
expectedAttribute =
AttributeBuilder.build(AccountAttribute.SHELL.getName(), "/nonsense/shell");
replaceAttrs = CollectionUtil.newSet(expectedAttribute);
try {
getFacade().update(ObjectClass.ACCOUNT, new Uid(getUsername()), replaceAttrs, null);
fail("Using bad shell value did not fail.");
} catch (Exception ex) {
// OK
}
// negative test: primary group
expectedAttribute =
AttributeBuilder.build(AccountAttribute.GROUP.getName(), "nonsensegroup");
replaceAttrs = CollectionUtil.newSet(expectedAttribute);
try {
getFacade().update(ObjectClass.ACCOUNT, new Uid(getUsername()), replaceAttrs, null);
fail("Changing the primary group to an invalid value did not fail.");
} catch (Exception ex) {
// OK
}
// negative test: secondary group
if (!getConnection().isNis()) { // not applicable for NIS resources
replaceAttrs =
CollectionUtil.newSet(AttributeBuilder
.build(AccountAttribute.GROUP.getName() /*
* value is
* null
*/), AttributeBuilder.build(
AccountAttribute.SECONDARY_GROUP.getName(), "nonsensegroup"));
try {
getFacade().update(ObjectClass.ACCOUNT, new Uid(getUsername()), replaceAttrs, null);
fail("Changing the secondary group to an invalid value did not fail.");
} catch (Exception ex) {
// OK
}
}
// negative test: create
Set<Attribute> attrs =
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, "donaldduck"),
AttributeBuilder.buildPassword("sample_1".toCharArray()), AttributeBuilder
.build(AccountAttribute.SHELL.getName(), "/invalid/shell"));
try {
getFacade().create(ObjectClass.ACCOUNT, attrs, null);
fail("did not fail when creating user with invalid data");
} catch (Exception ex) {
// OK
} finally {
try {
getFacade().delete(ObjectClass.ACCOUNT, new Uid("sample_1"), null);
} catch (Exception ex) {
// OK
}
}
}
/**
* Error should be thrown when attemting to create a user using an already
* existing username. (Usernames are inherently unique on Unix).
*/
@Test
public void testDuplicateCreate() {
Set<Attribute> attrs =
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, getUsername()), // duplicate
// username
AttributeBuilder.buildPassword("sample_1".toCharArray()));
try {
getFacade().create(ObjectClass.ACCOUNT, attrs, null);
fail("expected exception on create of user with non-unique username (==uid)");
} catch (Exception ex) {
// OK
}
}
/**
* Test for false positives during account deletions.
*/
@Test
public void testUserDeletion() {
if (getConnection().isNis()) {
// Workaround: skipping. TODO Solaris NIS scripts doesn't throw an
// error during this test.
logger.info("skipping test 'testUserDeletion' for Solaris NIS configuration.");
return;
}
// special configuration for this test + facade
SolarisConfiguration config = SolarisTestCommon.createConfiguration();
// Set up the resource to not make the home directory but attempt to
// remove it when the
// user is deleted.
config.setMakeDirectory(false);
config.setDeleteHomeDirectory(true);
ConnectorFacade facade = SolarisTestCommon.createConnectorFacade(config);
String username = "mrbean";
String password = "sno0py";
facade.create(ObjectClass.ACCOUNT, CollectionUtil.newSet(AttributeBuilder.build(Name.NAME,
username), AttributeBuilder.buildPassword(password.toCharArray())), null);
try {
String command =
(!getConnection().isNis()) ? "logins -oxma -l " + username : "ypmatch "
+ username + " passwd";
String out = getConnection().executeCommand(command);
assertTrue(out.contains(username), "user '" + username + "' is missing");
try {
facade.delete(ObjectClass.ACCOUNT, new Uid(username), null);
fail("deleted the user when deletion should have failed.");
} catch (Exception ex) {
// OK
}
} finally {
// we are using the original facade here, because it has a correct
// configuration.
try {
getFacade().delete(ObjectClass.ACCOUNT, new Uid(username), null);
} catch (Exception ex) {
// OK
}
}
}
/**
* Test create, update, search, delete operations on
* {@link ObjectClass#GROUP}
*/
@Test
public void testGroupCRUD() {
doGroupCRUD(getConfiguration().isSudoAuthorization());
}
private void doGroupCRUD(boolean isSudoAuthorization) {
final String groupName = "conngroup";
// this user belongs to group named after groupName above.
final String belongingUser = getUsername();
final String groupNameForUpdate = "connnewgroup";
try {
cleanGroup(groupName);
cleanGroup(groupNameForUpdate);
} catch (Exception ex) {
fail("Failed to clean up preliminary groups.");
}
final Set<Attribute> groupAttrs =
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, groupName),
AttributeBuilder.build(GroupAttribute.USERS.getName(), getUsername(),
belongingUser));
getFacade().create(ObjectClass.GROUP, groupAttrs, null);
// creating a duplicate group, exception expected.
try {
getFacade().create(ObjectClass.GROUP, groupAttrs, null);
fail("Attempt to create a duplicate group should fail.");
} catch (Exception ex) {
// OK
}
// retrieve the recently created group
ToListResultsHandler handler = new ToListResultsHandler();
Set<String> attributesToGet = CollectionUtil.newSet(GroupAttribute.GID.getName());
if (!getConnection().isNis()) {
attributesToGet.add(GroupAttribute.USERS.getName());
}
getFacade().search(ObjectClass.GROUP,
FilterBuilder.equalTo(AttributeBuilder.build(Name.NAME, groupName)), handler,
new OperationOptionsBuilder().setAttributesToGet(attributesToGet).build());
List<ConnectorObject> result = handler.getObjects();
assertTrue(result.size() > 0, "failed retrieving group: " + groupName);
ConnectorObject co = result.get(0);
if (!getConnection().isNis()) {
Attribute usersAttr = co.getAttributeByName(GroupAttribute.USERS.getName());
assertNotNull(usersAttr);
boolean found = false;
for (Object it : usersAttr.getValue()) {
if (it.toString().equals(belongingUser)) {
found = true;
break;
}
}
String msg =
String.format("user '%s' is missing from group '%s'.", belongingUser, groupName);
assertTrue(found, msg);
}
Attribute gidAttr = co.getAttributeByName(GroupAttribute.GID.getName());
assertNotNull(gidAttr);
Integer gid = AttributeUtil.getIntegerValue(gidAttr);
Assert.assertNotNull(gid);
// Create a new group object with a duplicate gid, this one should fail
try {
getFacade().create(
ObjectClass.GROUP,
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, groupNameForUpdate),
AttributeBuilder.build(GroupAttribute.GID.getName(), gid)), null);
fail("improperly create a gropu with duplicate id, exception should be thrown.");
} catch (Exception ex) {
// OK
}
// Update the the group that was just created
// TODO to be continued.
}
private void cleanGroup(String groupName) {
ToListResultsHandler handler = new ToListResultsHandler();
getFacade().search(ObjectClass.GROUP,
FilterBuilder.equalTo(AttributeBuilder.build(Name.NAME, groupName)), handler, null);
if (handler.getObjects().size() > 0) {
getFacade().delete(ObjectClass.GROUP, new Uid(groupName), null);
}
}
/**
* Error should be thrown when changing the "uid" of the user to // the same
* value of the another existing user in the resource.
*/
@Test
public void testDuplicateUpdate() {
Set<Attribute> replaceAttrs =
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, getSecondUsername())); // duplicate
// username
try {
getFacade().update(ObjectClass.ACCOUNT, new Uid(getUsername()), replaceAttrs, null);
fail("expected exception on update of user with non-unique username (==uid)");
} catch (Exception ex) {
// OK
}
}
/**
* check if the user has the given attribute set to the given value.
*
* @param username
* the accountid to check
* @param expectedAttribute
* sources of attribute name/value to check
* @return true if the attribute is set, false otherwise.
*/
private boolean checkUser(String username, Attribute expectedAttribute) {
ToListResultsHandler handler = new ToListResultsHandler();
getFacade().search(
ObjectClass.ACCOUNT,
FilterBuilder.equalTo(AttributeBuilder.build(Name.NAME, username)),
handler,
new OperationOptionsBuilder().setAttributesToGet(expectedAttribute.getName())
.build());
List<ConnectorObject> l = handler.getObjects();
assertTrue(l.size() == 1, "the requested attribute is missing");
ConnectorObject co = l.get(0);
Attribute attr = co.getAttributeByName(expectedAttribute.getName());
// workaround for shell attribute, as sometimes /bin/sh is a simlink to
// /sbin/sh,
// and the system treats it as the same shell. So we should compare only
// stuff after
// the last slash.
if (attr.getName().equals(AccountAttribute.SHELL.getName())) {
String expectedShell = AttributeUtil.getStringValue(expectedAttribute);
String actualShell = AttributeUtil.getStringValue(attr);
int i = expectedShell.lastIndexOf("/");
if (i != -1) {
expectedShell = expectedShell.substring(i + 1);
}
i = actualShell.lastIndexOf("/");
if (i != -1) {
actualShell = actualShell.substring(i + 1);
}
expectedShell = expectedShell.trim();
actualShell = actualShell.trim();
return expectedShell.equals(actualShell);
}
return CollectionUtil.equals(attr.getValue(), expectedAttribute.getValue());
}
/**
* Update to an existing Uid should throw exception.
*/
@Test
public void testDuplicateUid() {
// check if both of users exist:
checkUser(getUsername(), AttributeBuilder.build(Name.NAME, getUsername()));
checkUser(getSecondUsername(), AttributeBuilder.build(Name.NAME, getSecondUsername()));
// fetch the uid of first user:
String loginsCmd =
(!getConnection().isNis()) ? "logins -oxma -l " + getUsername() : "ypmatch \""
+ getUsername() + "\" passwd";
String out = getConnection().executeCommand(loginsCmd);
String firstUid = out.split(":")[1];
// update second users' uid to the first:
try {
getFacade().update(
ObjectClass.ACCOUNT,
new Uid(getSecondUsername()),
CollectionUtil.newSet(AttributeBuilder.build(AccountAttribute.UID.getName(),
firstUid)), null);
fail("Update of 'solaris uid' attribute the with an existing uid should throw RuntimeException.");
} catch (RuntimeException ex) {
// OK
}
}
/**
* Password shouldn't contain control sequence characters
*/
@Test
public void testControlCharPassword() {
List<Character> controlChars = getControlChars();
// basic test of connection, if it filters invalid password
for (Character controlCharacter : controlChars) {
try {
String passwd =
new StringBuilder().append("foo").append(controlCharacter).append("bar")
.toString();
getConnection().sendPassword(new GuardedString(passwd.toCharArray()));
fail("Exception should be thrown when attempt to send control chars within the password");
} catch (RuntimeException ex) {
// OK
}
}
}
/**
* analogical to {@link SolarisConnectorTest#testControlCharPassword()}, but
* for create.
*/
@Test
public void testCreateControlCharPasswd() {
String dummyUser = "bugsbunny";
try {
String controlChar = "\r";
String password =
new StringBuilder().append("foo").append(controlChar).append("bar").toString();
getFacade().create(
ObjectClass.ACCOUNT,
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, dummyUser),
AttributeBuilder.buildPassword(password.toCharArray())), null);
fail("Exception should be thrown when password containing control char sent.");
} catch (RuntimeException ex) {
// OK
} finally {
try {
getFacade().delete(ObjectClass.ACCOUNT, new Uid(dummyUser), null);
} catch (Exception ex) {
// OK
}
}
}
/**
* analogical to {@link SolarisConnectorTest#testControlCharPassword()}, but
* for update.
*/
@Test
public void testUpdateControlCharPasswd() {
String controlChar = "\n";
String password =
new StringBuilder().append("fo0").append(controlChar).append("bar").toString();
try {
getFacade().update(ObjectClass.ACCOUNT, new Uid(getUsername()),
CollectionUtil.newSet(AttributeBuilder.buildPassword(password.toCharArray())),
null);
fail("Exception should be thrown when password containing control char sent.");
} catch (RuntimeException ex) {
// OK
}
}
@Test
public void testCreateUidWithNonUniqueValue() {
final String username2 = getSecondUsername();
final String dummyUser = "porkyPig";
try {
getFacade().create(
ObjectClass.ACCOUNT,
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, dummyUser),
AttributeBuilder.buildPassword("foopass".toCharArray()),
AttributeBuilder.build(AccountAttribute.UID.getName(), username2)),
null);
fail("Create of user ID with existing uid should fail - throw an exception.");
} catch (RuntimeException ex) {
// OK
System.out.println(ex.toString());
} finally {
try {
getFacade().delete(ObjectClass.ACCOUNT, new Uid(dummyUser), null);
} catch (Exception ex) {
// OK
}
}
}
private List<Character> getControlChars() {
List<Character> controlChars = CollectionUtil.<Character> newList();
for (char i = 0; i <= 0x001F; i++) {
controlChars.add(i);
}
for (char i = 0x007F; i <= 0x009F; i++) {
controlChars.add(i);
}
return controlChars;
}
@Override
public boolean createGroup() {
return false;
}
@Override
public int getCreateUsersNumber() {
return 2;
}
private String getUsername() {
return getUsername(0);
}
private String getSecondUsername() {
return getUsername(1);
}
}