/*
* ====================
* 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.attr;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.identityconnectors.common.CollectionUtil;
import org.identityconnectors.common.Pair;
import org.identityconnectors.common.StringUtil;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
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.operation.PasswdCommandTest;
import org.identityconnectors.solaris.test.SolarisTestBase;
import org.identityconnectors.test.common.ToListResultsHandler;
import org.testng.AssertJUnit;
import org.testng.SkipException;
import org.testng.annotations.Test;
/**
* Hub for {@link AccountAttribute} tests.
*
* Password related account attributes are tested in {@link PasswdCommandTest},
* these attributes are:
* <ul>
* <li>{@link AccountAttribute#LOCK}</li>
* <li>{@link AccountAttribute#MAX}</li>
* <li>{@link AccountAttribute#MIN}</li>
* <li>{@link AccountAttribute#WARN}</li>
* </ul>
*
* The role-based access control related attributes are tested in
* {@link RBACAttributeTests}, these attributes are:
* <ul>
* <li>{@link AccountAttribute#ROLES}</li>
* <li>{@link AccountAttribute#AUTHORIZATION}</li>
* <li>{@link AccountAttribute#PROFILE}</li>
* </ul>
*
* @author David Adam
*
*/
public class AccountAttributeTest extends SolarisTestBase {
private static final Log logger = Log.getLog(AccountAttributeTest.class);
@Test
public void testDir() {
genericTest(AccountAttribute.DIR, CollectionUtil.newList("/home/hercules"), CollectionUtil
.newList("/"), "hercules");
}
@Test
public void testComment() {
genericTest(AccountAttribute.COMMENT, CollectionUtil.newList("myComment"), CollectionUtil
.newList("my comment 2"), "comentus");
}
@Test
public void testShell() {
genericTest(AccountAttribute.SHELL, CollectionUtil.newList("/bin/ksh"), CollectionUtil
.newList("/bin/csh"), "shellusr");
}
@Test
public void testGroup() {
genericTest(AccountAttribute.GROUP, CollectionUtil.newList("root"), CollectionUtil
.newList(getGroupName()), "cmark");
}
@Test
public void testSecondaryGroup() {
genericTest(AccountAttribute.SECONDARY_GROUP, CollectionUtil.newList("root"),
CollectionUtil.newList("root", getGroupName()), "cmark");
}
private static final Equalable DATE_COMPARATOR = new Equalable() {
// compare two strings, but ignore the '/' delimiters of the date. For
// example:
// equals("1/1/2010", "112010") == true
// equals("1/2/2010", "112010") == false
public boolean equals(List<? extends Object> o1, List<? extends Object> o2) {
AssertJUnit.assertTrue(o1.size() == 1 && o2.size() == 1);
String first = o1.get(0).toString();
String second = o2.get(0).toString();
first = reformat(first);
second = reformat(second);
return first.equals(second);
}
private String reformat(String dateString) {
dateString = dateString.replaceAll("/", "").trim();
if (dateString.startsWith("0") && dateString.length() > 1) {
dateString = dateString.substring(1);
}
return dateString;
}
};
@Test
public void testInactive() {
genericTest(AccountAttribute.INACTIVE, CollectionUtil.newList(13), CollectionUtil
.newList(3), "cmark");
}
/**
* Inactive attribute must be some future date, positive test.
*/
@Test
public void testExpire() {
// fetch the current date, and generate two dates in the future.
String currentDate = getConnection().executeCommand("date");
// assuming that last 4-digit number of the output is the date.
Matcher m = Pattern.compile("\\d\\d\\d\\d").matcher(currentDate);
int thisYear = -1;
if (m.find()) {
thisYear = Integer.valueOf(currentDate.substring(m.start(), m.end()));
} else {
AssertJUnit.fail("wrong date received, no 4-digit year present: " + currentDate);
}
String createDate = formatTestDate(thisYear + 3);
String updateDate = formatTestDate(thisYear + 4);
genericTest(AccountAttribute.EXPIRE, CollectionUtil.newList(createDate), CollectionUtil
.newList(updateDate), "cmark", DATE_COMPARATOR);
}
/**
* create a first January date for given year in Unix format, acceptable for
* {@link NativeAttribute#USER_INACTIVE} tests.
*/
private String formatTestDate(int yearInt) {
String year = Integer.valueOf(yearInt).toString();
// user last 2 digits of the year for the date.
String shortYear = year.substring(year.length() - 2);
return String.format("01/02/%s", shortYear);
}
/**
* Negative test: for inactive any past date should result in failure.
*/
@Test
public void testExpireNegative() {
try {
genericTest(AccountAttribute.EXPIRE, CollectionUtil.newList("01/01/84"/*
* a
* past
* date
*/),
CollectionUtil.newList("01/01/80"), "cmark", DATE_COMPARATOR);
AssertJUnit.fail("past date should fail");
} catch (ConnectorException ex) {
// OK
}
}
/**
* check behaviour of {@link AccountAttribute#PASSWD_FORCE_CHANGE}
* attribute.
*
* If it is true, the user should change her password on the next login,
* thus the "new password:" prompt signalizes this fact.
*/
@Test
public void testResetPassword() {
if (getConnection().isNis()) {
// Workaround: skipping. TODO Solaris NIS scripts in connector
// doesn't support forcing password change on Solaris NIS.
logger.info("skipping test 'testResetPassword' for Solaris NIS configuration.");
throw new SkipException(
"Skipping test 'testResetPassword' for Solaris NIS configuration.");
}
final Pair<String, String> credentials = createResetPasswordUser(true);
final String username = credentials.first;
final String password = credentials.second;
try {
// check if user exists
String loginsCmd =
(!getConnection().isNis()) ? "logins -oxma -l " + username : "ypmatch \""
+ username + "\" passwd";
String out = getConnection().executeCommand(loginsCmd);
assertTrue(out.contains(username), "user " + username + " is missing, buffer: <" + out
+ ">");
try {
getFacade().authenticate(ObjectClass.ACCOUNT, username,
new GuardedString(password.toCharArray()), null);
fail("expected to wait for 'new password:' prompt failed.");
} catch (ConnectorException ex) {
if (!ex.getMessage().contains("New Password:")) {
fail("expected to wait for 'new password:' prompt failed with exception: "
+ ex.getMessage());
} else {
logger.ok("test testResetPassword passed");
}
}
} finally {
getFacade().delete(ObjectClass.ACCOUNT, new Uid(username), null);
}
}
/** returns the username and password used for creation. */
private Pair<String, String> createResetPasswordUser(boolean isForceChange) {
final String username = "porkyPig";
final String password = "Passw0rd";
Set<Attribute> attrs =
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, username), AttributeBuilder
.buildPassword(password.toCharArray()), AttributeBuilder.build(
AccountAttribute.PASSWD_FORCE_CHANGE.getName(), isForceChange));
// cleanup the user if it's there from previous runs
try {
getFacade().delete(ObjectClass.ACCOUNT, new Uid(username), null);
} catch (Exception ex) {
// OK
}
getFacade().create(ObjectClass.ACCOUNT, attrs, null);
enableTrustedLogin(username);
return new Pair<String, String>(username, password);
}
@Test
public void testTimeLastLogin() {
if (getConnection().isNis()) {
logger.info("skipping test 'testTimeLastLogin' for NIS configuration, as it doesn't support attribute: "
+ AccountAttribute.TIME_LAST_LOGIN.getName());
return;
}
String username = "connuser";
String password = "blueray1";
getFacade().create(
ObjectClass.ACCOUNT,
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, username), AttributeBuilder
.buildPassword(password.toCharArray())), null);
enableTrustedLogin(username);
try {
// this involves doing 'login'
getFacade().authenticate(ObjectClass.ACCOUNT, username,
new GuardedString(password.toCharArray()), null);
// get the date
String out = getConnection().executeCommand("date");
AssertJUnit.assertTrue(StringUtil.isNotBlank(out));
String month = out.split(" ")[1];
// check that last attribute contains the month of last login.
ToListResultsHandler handler = new ToListResultsHandler();
getFacade().search(
ObjectClass.ACCOUNT,
FilterBuilder.equalTo(AttributeBuilder.build(Name.NAME, username)),
handler,
new OperationOptionsBuilder().setAttributesToGet(
AccountAttribute.TIME_LAST_LOGIN.getName()).build());
AssertJUnit.assertTrue(handler.getObjects().size() >= 1);
ConnectorObject co = handler.getObjects().get(0);
Attribute lastAttr = co.getAttributeByName(AccountAttribute.TIME_LAST_LOGIN.getName());
String lastValue = AttributeUtil.getStringValue(lastAttr);
String msg =
String.format(
"expected to found the current login's month '%s' in the value of %s attribute command '%s', but it is missing.",
AccountAttribute.TIME_LAST_LOGIN.getName(), month, lastValue);
AssertJUnit.assertTrue(msg, StringUtil.isNotBlank(lastValue)
&& lastValue.contains(month));
} finally {
getFacade().delete(ObjectClass.ACCOUNT, new Uid(username), null);
}
}
@Override
public boolean createGroup() {
return true;
}
@Override
public int getCreateUsersNumber() {
return 0;
}
private <E> void genericTest(AccountAttribute attr, List<E> createValue, List<E> updateValue,
String username) {
genericTest(attr, createValue, updateValue, username, new Equalable() {
public boolean equals(List<? extends Object> o1, List<? extends Object> o2) {
return o1.equals(o2);
}
});
}
private <E> void genericTest(AccountAttribute attr, List<E> createValue, List<E> updateValue,
String username, Equalable eq) {
// the account should be brand new
ToListResultsHandler handler = new ToListResultsHandler();
getFacade().search(
ObjectClass.ACCOUNT,
FilterBuilder.equalTo(AttributeBuilder.build(Name.NAME, username)),
handler,
new OperationOptionsBuilder().setAttributesToGet(CollectionUtil.newSet(Name.NAME))
.build());
if (handler.getObjects().size() >= 1) {
throw new RuntimeException("Please provide a brand new accountname, account '"
+ username + "' already exits");
}
// create a new account with create value
try {
// create can throw exceptions even because the password aging is
// disabled, but even than we need to do a cleanup of the created
// account.
getFacade().create(
ObjectClass.ACCOUNT,
CollectionUtil.newSet(AttributeBuilder.build(Name.NAME, username),
AttributeBuilder.build(attr.getName(), createValue)), null);
// check if create value was set
handler = new ToListResultsHandler();
getFacade().search(ObjectClass.ACCOUNT,
FilterBuilder.equalTo(AttributeBuilder.build(Name.NAME, username)), handler,
new OperationOptionsBuilder().setAttributesToGet(attr.getName()).build());
AssertJUnit.assertTrue(handler.getObjects().size() > 0);
AssertJUnit.assertTrue(eq.equals(createValue, handler.getObjects().get(0)
.getAttributeByName(attr.getName()).getValue()));
// update the value
getFacade().update(ObjectClass.ACCOUNT, new Uid(username),
CollectionUtil.newSet(AttributeBuilder.build(attr.getName(), updateValue)),
null);
// check if update value was set
handler = new ToListResultsHandler();
getFacade().search(ObjectClass.ACCOUNT,
FilterBuilder.equalTo(AttributeBuilder.build(Name.NAME, username)), handler,
new OperationOptionsBuilder().setAttributesToGet(attr.getName()).build());
AssertJUnit.assertTrue(handler.getObjects().size() > 0);
AssertJUnit.assertTrue(eq.equals(updateValue, handler.getObjects().get(0)
.getAttributeByName(attr.getName()).getValue()));
} finally {
try {
getFacade().delete(ObjectClass.ACCOUNT, new Uid(username), null);
} catch (Exception ex) {
// OK
}
}
}
private interface Equalable {
public boolean equals(List<? extends Object> o1, List<? extends Object> o2);
}
}