package org.identityconnectors.solaris.mode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.identityconnectors.common.CollectionUtil;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeInfo;
import org.identityconnectors.framework.common.objects.AttributeInfo.Flags;
import org.identityconnectors.framework.common.objects.AttributeInfoBuilder;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.ObjectClassInfo;
import org.identityconnectors.framework.common.objects.ObjectClassInfoBuilder;
import org.identityconnectors.framework.common.objects.OperationalAttributeInfos;
import org.identityconnectors.framework.common.objects.Schema;
import org.identityconnectors.framework.common.objects.SchemaBuilder;
import org.identityconnectors.framework.spi.operations.AuthenticateOp;
import org.identityconnectors.framework.spi.operations.CreateOp;
import org.identityconnectors.framework.spi.operations.DeleteOp;
import org.identityconnectors.framework.spi.operations.ResolveUsernameOp;
import org.identityconnectors.framework.spi.operations.SchemaOp;
import org.identityconnectors.framework.spi.operations.UpdateOp;
import org.identityconnectors.solaris.SolarisConnection;
import org.identityconnectors.solaris.SolarisConnector;
import org.identityconnectors.solaris.attr.AccountAttribute;
import org.identityconnectors.solaris.attr.ConnectorAttribute;
import org.identityconnectors.solaris.attr.GroupAttribute;
import org.identityconnectors.solaris.attr.NativeAttribute;
import org.identityconnectors.solaris.operation.CommandSwitches;
import org.identityconnectors.solaris.operation.search.SolarisEntry;
import org.identityconnectors.solaris.operation.search.SolarisSearch;
public class AixModeDriver extends UnixModeDriver{
public static final String MODE_NAME = "aix";
private static final String TMPFILE = "/tmp/connloginsError.$$";
private static final String SHELL_CONT_CHARS = "> ";
private static final int CHARS_PER_LINE = 160;
private static final Log logger = Log.getLog(AixModeDriver.class);
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-DD");
public AixModeDriver(SolarisConnection conn) {
super(conn);
}
@Override
public SolarisEntry buildAccountEntry(String username,
Set<NativeAttribute> attrsToGet) {
boolean isLast = attrsToGet.contains(NativeAttribute.LAST_LOGIN);
conn.doSudoStart();
String out = null;
try {
conn.executeCommand(conn.buildCommand(false, "rm -f", TMPFILE));
StringBuilder scriptBuilder = new StringBuilder();
scriptBuilder.append(conn.buildCommand(true, "lsuser ", username));
if (isLast) {
// TODO
}
String script = scriptBuilder.toString();
logger.info("Script: {0}", script);
out = conn.executeCommand(script, conn.getConfiguration().getBlockFetchTimeout());
logger.info("OUT: {0}", out);
conn.executeCommand(conn.buildCommand(false, "rm -f", TMPFILE));
} finally {
conn.doSudoReset();
}
SolarisEntry entry = buildUser(username, out);
return entry;
}
@Override
public List<SolarisEntry> buildAccountEntries(List<String> blockUserNames,
boolean isLast) {
conn.doSudoStart();
String out = null;
try {
conn.executeCommand(conn.buildCommand(false, "rm -f", TMPFILE));
String getUsersScript = buildGetUserScript(blockUserNames, isLast);
logger.info("Script: {0}", getUsersScript);
out =
conn.executeCommand(getUsersScript, conn.getConfiguration()
.getBlockFetchTimeout());
logger.info("OUT: {0}", out);
conn.executeCommand(conn.buildCommand(false, "rm -f", TMPFILE));
} finally {
conn.doSudoReset();
}
List<SolarisEntry> fetchedEntries = processOutput(out, blockUserNames, isLast);
if (fetchedEntries.size() != blockUserNames.size()) {
throw new RuntimeException("ERROR: expecting to return " + blockUserNames.size()
+ " instead of " + fetchedEntries.size());
// TODO possibly compare by content.
}
return fetchedEntries;
}
/** retrieve account info from the output. */
private List<SolarisEntry> processOutput(String out, List<String> blockUserNames, boolean isLast) {
// SVIDRA# getUsersFromCaptureList(CaptureList captureList, ArrayList
// users)()
List<String> lines = Arrays.asList(out.split("\n"));
Iterator<String> it = lines.iterator();
int captureIndex = 0;
List<SolarisEntry> result = new ArrayList<SolarisEntry>(blockUserNames.size());
while (it.hasNext()) {
final String currentAccount = blockUserNames.get(captureIndex);
String line = it.next();
// String lastLoginLine = null;
// Weed out shell continuation chars
if (line.startsWith(SHELL_CONT_CHARS)) {
int index = line.lastIndexOf(SHELL_CONT_CHARS);
line = line.substring(index + SHELL_CONT_CHARS.length());
}
SolarisEntry entry = buildUser(currentAccount, line);
if (entry != null) {
result.add(entry);
}
captureIndex++;
} // while (it.hasNext())
return result;
}
/**
* build user based on the content given.
*
* @param loginsLine
* @param lastLoginLine
* @return the build user.
*/
private SolarisEntry buildUser(String username, String loginsLine) {
List<String> attributesValues = Arrays.asList(loginsLine.split(" "));
SolarisEntry.Builder entryBuilder = new SolarisEntry.Builder(username).addAttr(NativeAttribute.NAME, username);
for (String aV : attributesValues){
String[] vals = aV.split("=");
if (vals.length != 2){
continue;
}
String attrName = vals[0];
String attrValue = vals[1];
if (attrName.equals("groups")){
List<String> values = Arrays.asList(attrValue.split(","));
entryBuilder.addAttr(NativeAttribute.GROUP_PRIM, values);
continue;
}
if (attrName.equals("id")){
entryBuilder.addAttr(NativeAttribute.ID, attrValue);
continue;
}
if (attrName.equals("home")){
entryBuilder.addAttr(NativeAttribute.DIR, attrValue);
continue;
}
ConnectorAttribute name = AccountAttribute.forAttributeName(attrName);
if (name == null){
continue;
}
List<String> values = Arrays.asList(attrValue.split(","));
entryBuilder.addAttr(name.getNative(), values);
}
return entryBuilder.build();
}
private String buildGetUserScript(List<String> blockUserNames, boolean isLast) {
// make a list of users, separated by space.
StringBuilder connUserList = new StringBuilder();
int charsThisLine = 0;
for (String user : blockUserNames) {
final int length = user.length();
// take care that line meets the limit on 160 chars per line
if ((charsThisLine + length + 3) > CHARS_PER_LINE) {
connUserList.append("\n");
charsThisLine = 0;
}
connUserList.append(user);
connUserList.append(" ");
charsThisLine += length + 1;
}
StringBuilder scriptBuilder = new StringBuilder();
scriptBuilder.append("WSUSERLIST=\"");
scriptBuilder.append(connUserList.toString() + "\n\";");
scriptBuilder.append("for user in $WSUSERLIST; do ");
String getScript = conn.buildCommand(true, "lsuser") + "$user; ";
scriptBuilder.append(getScript);
if (isLast) {
// TODO
}
scriptBuilder.append("done");
return scriptBuilder.toString();
}
@Override
public String buildPasswdCommand(String username) {
return conn.buildCommand(true, "pwdadm", username);
}
@Override
public void configurePasswordProperties(SolarisEntry entry,
SolarisConnection conn) {
Map<NativeAttribute, String> passwdSwitches = buildPasswdSwitches(entry, conn);
final String cmdSwitches =
CommandSwitches.formatCommandSwitches(entry, conn, passwdSwitches);
if (cmdSwitches.length() == 0) {
return; // no password related attribute present in the entry.
}
try {
final String command = conn.buildCommand(true, "pwdadm", cmdSwitches, entry.getName());
final String out = conn.executeCommand(command);
final String loweredOut = out.toLowerCase();
if (loweredOut.contains("usage:") || loweredOut.contains("password aging is disabled")
|| loweredOut.contains("command not found")) {
throw new ConnectorException(
"Error during configuration of password related attributes. Buffer content: <"
+ out + ">");
}
} catch (Exception ex) {
throw ConnectorException.wrap(ex);
}
}
private Map<NativeAttribute, String> buildPasswdSwitches(SolarisEntry entry,
SolarisConnection conn) {
Map<NativeAttribute, String> passwdSwitches =
new EnumMap<NativeAttribute, String>(NativeAttribute.class);
passwdSwitches.put(NativeAttribute.PWSTAT, "-f");
// passwdSwitches.put(NativeAttribute.PW_LAST_CHANGE, null); // this is
// not used attribute (see LoginsCommand and its SVIDRA counterpart).
// TODO erase this comment.
passwdSwitches.put(NativeAttribute.MIN_DAYS_BETWEEN_CHNG, "-n");
passwdSwitches.put(NativeAttribute.MAX_DAYS_BETWEEN_CHNG, "-x");
passwdSwitches.put(NativeAttribute.DAYS_BEFORE_TO_WARN, "-w");
String lockFlag = null;
Attribute lock = entry.searchForAttribute(NativeAttribute.LOCK);
if (lock != null) {
Object lockValue = AttributeUtil.getSingleValue(lock);
if (lockValue == null) {
throw new IllegalArgumentException("missing value for attribute LOCK");
}
boolean isLock = (Boolean) lockValue;
if (isLock) {
lockFlag = "-l";
} else {
// *unlocking* differs in Solaris 8,9 and in Solaris 10+:
lockFlag = (conn.isVersionLT10()) ? "-df" : "-u";
passwdSwitches.put(NativeAttribute.LOCK, lockFlag);
}
}
if (lockFlag != null) {
passwdSwitches.put(NativeAttribute.LOCK, lockFlag);
}
return passwdSwitches;
}
@Override
public Set<String> getRequiredCommands() {
return CollectionUtil.newSet(
// user
"last", "mkuser"); /*",usermod", "userdel", "passwd",*/
// group
// "groupadd", "groupmod", "groupdel");
}
@Override
public Schema buildSchema() {
final SchemaBuilder schemaBuilder = new SchemaBuilder(SolarisConnector.class);
/*
* GROUP
*/
Set<AttributeInfo> attributes = new HashSet<AttributeInfo>();
// attributes.add(Name.INFO);
for (GroupAttribute attr : GroupAttribute.values()) {
switch (attr) {
case USERS:
attributes.add(AttributeInfoBuilder.build(attr.getName(), String.class, EnumSet
.of(Flags.MULTIVALUED)));
break;
case GROUPNAME:
attributes.add(AttributeInfoBuilder.build(attr.getName(), String.class, EnumSet
.of(Flags.REQUIRED)));
break;
default:
attributes.add(AttributeInfoBuilder.build(attr.getName()));
break;
} // switch
} // for
// GROUP supports no authentication:
final ObjectClassInfo ociInfoGroup =
new ObjectClassInfoBuilder().setType(ObjectClass.GROUP_NAME).addAllAttributeInfo(
attributes).build();
schemaBuilder.defineObjectClass(ociInfoGroup);
schemaBuilder.removeSupportedObjectClass(AuthenticateOp.class, ociInfoGroup);
schemaBuilder.removeSupportedObjectClass(ResolveUsernameOp.class, ociInfoGroup);
/*
* ACCOUNT
*/
attributes = new HashSet<AttributeInfo>();
attributes.add(OperationalAttributeInfos.PASSWORD);
for (AccountAttribute attr : AccountAttribute.values()) {
String attrName = attr.getName();
AttributeInfo newAttr = null;
switch (attr) {
case NAME:
newAttr =
AttributeInfoBuilder.build(attrName, String.class, EnumSet
.of(Flags.REQUIRED));
break;
case MIN:
case MAX:
case WARN:
case INACTIVE:
case EXPIRE:
case UID:
newAttr = AttributeInfoBuilder.build(attrName, long.class);
break;
case PASSWD_FORCE_CHANGE:
newAttr =
AttributeInfoBuilder.build(attrName, boolean.class, EnumSet
.of(Flags.NOT_RETURNED_BY_DEFAULT));
break;
case SECONDARY_GROUP:
newAttr =
AttributeInfoBuilder.build(attrName, String.class, EnumSet
.of(Flags.MULTIVALUED));
break;
case ROLES:
case AUTHORIZATION:
case PROFILE:
newAttr = null;
break;
case TIME_LAST_LOGIN:
newAttr =
AttributeInfoBuilder.build(attrName, String.class, EnumSet.of(
Flags.NOT_UPDATEABLE, Flags.NOT_RETURNED_BY_DEFAULT));
break;
default:
newAttr = AttributeInfoBuilder.build(attrName);
break;
}
if (newAttr != null) {
attributes.add(newAttr);
}
}
tweakAccountActivationSchema(attributes);
final ObjectClassInfo ociInfoAccount =
new ObjectClassInfoBuilder().setType(ObjectClass.ACCOUNT_NAME).addAllAttributeInfo(
attributes).build();
schemaBuilder.defineObjectClass(ociInfoAccount);
/*
* SHELL
*/
attributes = new HashSet<AttributeInfo>();
attributes.add(AttributeInfoBuilder.build(SolarisSearch.SHELL.getObjectClassValue(),
String.class, EnumSet.of(Flags.MULTIVALUED, Flags.NOT_RETURNED_BY_DEFAULT,
Flags.NOT_UPDATEABLE)));
final ObjectClassInfo ociInfoShell =
new ObjectClassInfoBuilder().addAllAttributeInfo(attributes).setType(
SolarisSearch.SHELL.getObjectClassValue()).build();
schemaBuilder.defineObjectClass(ociInfoShell);
schemaBuilder.removeSupportedObjectClass(AuthenticateOp.class, ociInfoShell);
schemaBuilder.removeSupportedObjectClass(CreateOp.class, ociInfoShell);
schemaBuilder.removeSupportedObjectClass(UpdateOp.class, ociInfoShell);
schemaBuilder.removeSupportedObjectClass(DeleteOp.class, ociInfoShell);
schemaBuilder.removeSupportedObjectClass(SchemaOp.class, ociInfoShell);
schemaBuilder.removeSupportedObjectClass(ResolveUsernameOp.class, ociInfoShell);
return schemaBuilder.build();
}
@Override
public String getSudoPasswordRegexp() {
return "\\[sudo\\].*[pP]assword[^:]*:";
}
@Override
public String buildCreateUserCommand(SolarisEntry entry,
String commandSwitches) {
return conn.buildCommand(true, "mkuser", getAttributes(entry), entry.getName());
}
private String getAttributes(SolarisEntry entry){
Set<Attribute> attributes = entry.getAttributeSet();
StringBuilder buildedAttributes = new StringBuilder();
for (Attribute attr : attributes){
if (attr.getValue().isEmpty()){
continue;
}
if (NativeAttribute.NAME.getName().equals(attr.getName())){
continue;
}
Iterator<Object> values = attr.getValue().iterator();
StringBuilder valuesBuilder = new StringBuilder(String.valueOf(values.next()));
while (values.hasNext()){
valuesBuilder.append(",");
valuesBuilder.append(values.next());
}
String name = attr.getName();
for (AccountAttribute accountAttr : AccountAttribute.values()){
if (accountAttr.getNative().getName().equals(attr.getName())){
name = accountAttr.getName();
break;
}
}
buildedAttributes.append(name);
buildedAttributes.append("=");
buildedAttributes.append(valuesBuilder);
buildedAttributes.append(" ");
}
return buildedAttributes.toString();
}
@Override
public String buildUpdateUserCommand(SolarisEntry entry,
String commandSwitches) {
return conn.buildCommand(true, "chuser", getAttributes(entry), entry.getName());
}
@Override
public String getRenameDirScript(SolarisEntry entry, String newName) {
String renameDir =
"NEWNAME=" + newName + "; " +
"OLDNAME=" + entry.getName() + "; " +
"OLDDIR=`" + conn.buildCommand(true, "lsuser") + " $NEWNAME | cut -d: -f6`; " +
"OLDBASE=`basename $OLDDIR`; " +
"if [ \"$OLDNAME\" = \"$OLDBASE\" ]; then\n" +
"PARENTDIR=`dirname $OLDDIR`; " +
"NEWDIR=`echo $PARENTDIR/$NEWNAME`; " +
"if [ ! -s $NEWDIR ]; then " +
conn.buildCommand(true, "chown") + " $NEWNAME $OLDDIR; " +
conn.buildCommand(true, "mv") + " -f $OLDDIR $NEWDIR; " +
"if [ $? -eq 0 ]; then\n" +
conn.buildCommand(true, "usermod") + " -d $NEWDIR $NEWNAME; " +
"fi; " +
"fi; " +
"fi";
// @formatter:off
return renameDir;
}
@Override
public String formatDate(long daysSinceEpoch) {
return DATE_FORMAT.format(daysSinceEpoch*(24*60*60*1000));
}
}