/* * ==================== * 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]" * ==================== * Portions Copyrighted 2012 Evolveum, Radovan Semancik * Portions Copyrighted 2013 ForgeRock */ package org.identityconnectors.solaris; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import org.identityconnectors.common.CollectionUtil; 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.exceptions.UnknownUidException; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.AttributeUtil; import org.identityconnectors.framework.common.objects.ConnectorObject; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.OperationOptionsBuilder; import org.identityconnectors.framework.common.objects.ResultsHandler; import org.identityconnectors.framework.common.objects.Schema; import org.identityconnectors.framework.common.objects.ScriptContext; import org.identityconnectors.framework.common.objects.Uid; import org.identityconnectors.framework.common.objects.filter.FilterTranslator; import org.identityconnectors.framework.spi.Configuration; import org.identityconnectors.framework.spi.ConnectorClass; import org.identityconnectors.framework.spi.PoolableConnector; 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.ScriptOnResourceOp; import org.identityconnectors.framework.spi.operations.SearchOp; import org.identityconnectors.framework.spi.operations.TestOp; import org.identityconnectors.framework.spi.operations.UpdateOp; import org.identityconnectors.solaris.SolarisConnection.ErrorHandler; import org.identityconnectors.solaris.attr.NativeAttribute; import org.identityconnectors.solaris.operation.SolarisAuthenticate; import org.identityconnectors.solaris.operation.SolarisCreate; import org.identityconnectors.solaris.operation.SolarisDelete; import org.identityconnectors.solaris.operation.SolarisScriptOnConnector; import org.identityconnectors.solaris.operation.SolarisUpdate; import org.identityconnectors.solaris.operation.search.SolarisFilterTranslator; import org.identityconnectors.solaris.operation.search.SolarisSearch; import org.identityconnectors.solaris.operation.search.nodes.EqualsNode; import org.identityconnectors.solaris.operation.search.nodes.Node; import com.jcraft.jsch.JSch; /** * @author David Adam * */ @ConnectorClass(displayNameKey = "Solaris", configurationClass = SolarisConfiguration.class) public class SolarisConnector implements PoolableConnector, AuthenticateOp, SchemaOp, CreateOp, DeleteOp, UpdateOp, SearchOp<Node>, TestOp, ScriptOnResourceOp, ResolveUsernameOp { /** * Setup logging for the {@link SolarisConnector}. */ private static final Log logger = Log.getLog(SolarisConnector.class); // TODO: Use configuration for the pattern private static final Pattern USER_NAME = Pattern.compile("([a-zA-Z_][a-zA-Z0-9_]{0,30})"); private SolarisConnection connection; private SolarisConfiguration configuration; private Schema schema; /** * {@see org.identityconnectors.framework.spi.Connector#init(org. * identityconnectors .framework.spi.Configuration)}. */ public void init(Configuration cfg) { configuration = (SolarisConfiguration) cfg; connection = new SolarisConnection(configuration); JSch.setLogger(new JSchLogger()); } /** * {@see * org.identityconnectors.framework.spi.PoolableConnector#checkAlive()}. */ public void checkAlive() { logger.info("checkAlive()"); connection.checkAlive(); } /** * Disposes of {@link SolarisConnector}'s resources. * * {@see org.identityconnectors.framework.spi.Connector#dispose()} */ public void dispose() { logger.info("dispose()"); configuration = null; if (connection != null) { connection.dispose(); connection = null; } } /* *********************** OPERATIONS ************************** */ /** * {@inheritDoc} * <p> * attempts to authenticate the given user / password on configured Solaris * resource */ public Uid authenticate(ObjectClass objectClass, String username, GuardedString password, OperationOptions options) { if (!USER_NAME.matcher(username).matches()) { throw new UnknownUidException("Invalid username: " + username); } Uid uid = null; try { uid = new SolarisAuthenticate(this).authenticate(objectClass, username, password, options); } finally { // after unsuccessful authenticate the connection might be in an // unusable state. We have to create a new connection then. connection.dispose(); connection = null; } return uid; } /** {@inheritDoc} */ public Uid create(ObjectClass objectClass, Set<Attribute> attrs, OperationOptions options) { return new SolarisCreate(this).create(objectClass, attrs, options); } /** {@inheritDoc} */ public void delete(ObjectClass objectClass, Uid uid, OperationOptions options) { new SolarisDelete(this).delete(objectClass, uid, options); } public Uid update(ObjectClass objectClass, Uid uid, Set<Attribute> replaceAttributes, OperationOptions options) { return new SolarisUpdate(this).update(objectClass, uid, AttributeUtil.addUid( replaceAttributes, uid), options); } public void executeQuery(ObjectClass objectClass, Node query, ResultsHandler handler, OperationOptions options) { new SolarisSearch(this, objectClass, query, handler, options).executeQuery(); } public FilterTranslator<Node> createFilterTranslator(ObjectClass objectClass, OperationOptions options) { logger.info("creating Filter translator."); return new SolarisFilterTranslator(objectClass); } public Schema schema() { logger.info("schema()"); if (schema == null) { schema = connection.getModeDriver().buildSchema(); } return schema; } /* ********************** AUXILIARY METHODS ********************* */ /** * @throws Exception * if the test of connection was failed. */ public void test() { logger.info("test()"); configuration.validate(); checkAlive(); if (configuration.isCheckCommandsAvailability()) { testCheckCommandsAndPermissions(); } } /** * Check the resource and determine if the commands (used by the connector) * available and the account given has permission to execute them. */ private void testCheckCommandsAndPermissions() { getConnection().doSudoStart(); try { // determine if the required commands (by connector) are present Set<String> requiredCommands = getRequiredCommands(getConnection().isNis()); StringBuilder args = new StringBuilder(); for (String c : requiredCommands) { args.append(c).append(" "); } // The 'which' command needs sudo as it needs to probe root's path. // Also some commands may not be accessible to non-root user (e.g. // on RedHat-like systems). String whichCmd = getConnection().buildCommand(true, "which", args.toString()); String out = getConnection().executeCommand(whichCmd, CollectionUtil.newSet("which: not found")); final String no = "no "; final String in = " in "; for (String line : out.split("\n")) { final int indexOfNo = line.indexOf(no); final int indexOfIn = line.indexOf(in); if (line.contains(no) && line.contains(in) && indexOfNo < indexOfIn) { String param1 = line.substring(indexOfNo + no.length(), indexOfIn); String param2 = line.substring(indexOfIn + in.length()); throw new ConnectorException(String.format( "Failed to find '%s' in the path '%s'", param1, param2)); } } // Check if the connector has write access to /tmp folder final String tmpFileName = "/tmp/SConnectorTmpAccessTest"; out = getConnection().executeCommand("touch " + tmpFileName); if (StringUtil.isNotBlank(out)) { throw new ConnectorException("ERROR: non-empty output from 'touch' command: <" + out + ">"); } getConnection().executeCommand("rm -f " + tmpFileName); // Execute the read-only command (last) to see if they we have // permission to execute it Map<String, ErrorHandler> reject = CollectionUtil.<String, ErrorHandler> newMap( "[P,p]ermission|[d,D]enied|not allowed|Sorry,", new ErrorHandler() { public void handle(String buffer) { throw new ConnectorException( "Invalid resource configuration: permission denied for execution of \"last\" command. Buffer: <" + buffer + ">"); } }); getConnection().executeCommand("last -n 1", reject, Collections.<String> emptySet()); } finally { getConnection().doSudoReset(); } } private Set<String> getRequiredCommands(boolean isNis) { Set<String> result = CollectionUtil.newSet( // required file commands for all unix connectors "ls", "cp", "mv", "rm", "sed", "cat", "cut", "awk", "grep", "diff", "echo", "sort", "touch", "chown", "chmod", "sleep"); if (isNis) { result.addAll(CollectionUtil.newSet("ypcat", "ypmatch", "yppasswd")); } else { result.addAll(connection.getModeDriver().getRequiredCommands()); //moved to concrete unix mode drive // result.addAll(CollectionUtil.newSet( // // user // "last", "useradd", "usermod", "userdel", "passwd", // // group // "groupadd", "groupmod", "groupdel")); } return result; } /* ********************** GET / SET methods ********************* */ public SolarisConnection getConnection() { assert (connection != null); return connection; } /** * {@see org.identityconnectors.framework.spi.Connector#getConfiguration()}. */ public Configuration getConfiguration() { return configuration; } /** * supported scripting language is {@code /bin/sh} shell, that is present on * every Solaris resource. */ public Object runScriptOnResource(ScriptContext request, OperationOptions options) { return new SolarisScriptOnConnector(this).runScriptOnResource(request, options); } public Uid resolveUsername(ObjectClass objectClass, String username, OperationOptions options) { List<ConnectorObject> searchResult = null; if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { if (USER_NAME.matcher(username).matches()) { ToListResultsHandler handler = new ToListResultsHandler(); Node query = new EqualsNode(NativeAttribute.NAME, false, CollectionUtil .newList(username)); executeQuery(objectClass, query, handler, new OperationOptionsBuilder().build()); searchResult = handler.getObjects(); if (searchResult.isEmpty()) { throw new UnknownUidException(String.format( "userName: '%s' cannot be resolved", username)); } } else { throw new UnknownUidException("Invalid username: " + username); } } else { throw new UnsupportedOperationException("ObjectClass: '" + objectClass.getObjectClassValue() + "' is not supported by ResolveUsernameOp."); } return searchResult.get(0).getUid(); } static final class ToListResultsHandler implements ResultsHandler { private final List<ConnectorObject> objects = new ArrayList<ConnectorObject>(); public boolean handle(ConnectorObject object) { objects.add(object); return true; } public List<ConnectorObject> getObjects() { return objects; } } }