/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
*
* U.S. Government Rights - Commercial software. Government users
* are subject to the Sun Microsystems, Inc. standard license agreement
* and applicable provisions of the FAR and its supplements.
*
* Use is subject to license terms.
*
* This distribution may include materials developed by third parties.
* Sun, Sun Microsystems, the Sun logo, Java and Project Identity
* Connectors are trademarks or registered trademarks of Sun
* Microsystems, Inc. or its subsidiaries in the U.S. and other
* countries.
*
* UNIX is a registered trademark in the U.S. and other countries,
* exclusively licensed through X/Open Company, Ltd.
*
* -----------
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008 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://identityconnectors.dev.java.net/CDDLv1.0.html
* 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 identityconnectors/legal/license.txt.
* 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 ForgeRock Inc.
*
*/
package org.forgerock.openicf.connectors.googleapps;
import static org.identityconnectors.common.CollectionUtil.isEmpty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
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.AttributesAccessor;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.PredefinedAttributes;
import org.identityconnectors.framework.common.objects.ResultsHandler;
import org.identityconnectors.framework.common.objects.Uid;
import com.google.gdata.data.appsforyourdomain.Quota;
import com.google.gdata.data.appsforyourdomain.provisioning.UserEntry;
/**
*
* @author warrenstrange
*/
public class GoogleAppsUserOps {
private final GoogleAppsConnector gc;
private static final Log log = Log.getLog(GoogleAppsUserOps.class);
GoogleAppsUserOps(GoogleAppsConnector gc) {
this.gc = gc;
}
/**
* Create a user
*
* @param name
* @param a
* @return
*/
Uid createUser(Name name, AttributesAccessor a) {
final String accountId = a.getName().getNameValue();
final GoogleAppsClient g = gc.getClient();
// log.info("Extracting Attrs {0}", attrs);
// todo: this craters if the attribute does not exist (NPE)
// we should throw a better error
final String givenName = a.findString(GoogleAppsConnector.ATTR_GIVEN_NAME);
final String familyName = a.findString(GoogleAppsConnector.ATTR_FAMILY_NAME);
final Integer quota = a.findInteger(GoogleAppsConnector.ATTR_QUOTA);
// Password is Operational
GuardedString password = a.getPassword();
if (password == null) {
throw new IllegalArgumentException("The Password attribute cannot be null.");
}
final boolean suspended = !a.getEnabled(true);
String clearPw = getPlainPassword(password);
UserEntry ue =
g.setUserEntry(null, accountId, clearPw, givenName, familyName, suspended, quota);
log.info("Creating User {0} givenName: {1} familyName {2} ", accountId, givenName,
familyName);
ue = g.createUser(ue);
List<String> nicks = a.findStringList(GoogleAppsConnector.ATTR_NICKNAME_LIST);
if (nicks != null) {
for (String nickname : nicks) {
log.info("Adding nickname {0} to account {1}", nickname, accountId);
g.createNickname(accountId, nickname);
}
}
List<String> groups = a.findStringList(PredefinedAttributes.GROUPS_NAME);
if (!isEmpty(groups)) {
for (String group : groups) {
g.addGroupMember(group, accountId);
}
}
List<String> owners = a.findStringList(GoogleAppsConnector.ATTR_OWNER_LIST);
if (!isEmpty(owners)) {
for (String owner : owners) {
g.addGroupMember(owner, accountId);
}
}
return new Uid(accountId);
}
/**
* Utility method to unguard the password.
*
* @param password
* - Guarded password
* @return plain text password
*/
public static String getPlainPassword(GuardedString password) {
if (password == null) {
return null;
}
final StringBuffer buf = new StringBuffer();
password.access(new GuardedString.Accessor() {
public void access(char[] clearChars) {
buf.append(clearChars);
}
});
return buf.toString();
}
void delete(String id) {
GoogleAppsClient g = gc.getClient();
g.deleteUser(id);
}
void query(String query, ResultsHandler handler, OperationOptions ops) {
GoogleAppsClient g = gc.getClient();
boolean fetchNicknames = false; // by default nicknames and groups are
// not fetched
boolean fetchGroups = false;
// boolean fetchOwners = false;
if (ops != null) {
String attrs[] = ops.getAttributesToGet();
if (attrs != null) {
List<String> alist = Arrays.asList(attrs);
if (alist.contains(GoogleAppsConnector.ATTR_NICKNAME_LIST))
fetchNicknames = true;
if (alist.contains(PredefinedAttributes.GROUPS_NAME))
fetchGroups = true;
}
}
if (query == null) { // return all users;
log.info("Fetching All Users");
Iterator i = g.getIterator();
while (i.hasNext()) {
UserEntry ue = (UserEntry) i.next();
List<String> nicknames = new ArrayList<String>();
List<String> groups = new ArrayList<String>();
String accountId = ue.getLogin().getUserName();
if (fetchNicknames) {
nicknames = g.getNicknamesAsList(accountId);
}
if (fetchGroups) {
groups = g.getGroupMembershipsForUser(accountId);
}
handler.handle(makeConnectorObject(ue, nicknames, groups));
}
} else { // get a single user
ConnectorObject obj = get(query, fetchNicknames, fetchGroups);
log.info("ConnectorObj {0}", obj);
if (obj != null) {
handler.handle(obj);
}
}
}
/**
* Given a google apps user entry and associated nicknames, create a
* ConnectorObject.
*
* @param ue
* google apps UesrEntry object
* @param nicknames
* list of nicknames
* @param groups
* - list of groups this user belongs to
* @return a connectorOject
*/
private ConnectorObject makeConnectorObject(UserEntry ue, List<String> nicknames,
List<String> groups) {
if (ue == null)
return null; // must have a userentry object
ObjectClass objectClass = ObjectClass.ACCOUNT;
ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
Uid uid = new Uid(ue.getLogin().getUserName());
builder.setUid(uid);
builder.setName(uid.getUidValue());
String familyName = ue.getName().getFamilyName();
String givenName = ue.getName().getGivenName();
boolean suspended = ue.getLogin().getSuspended().booleanValue();
builder.addAttribute(AttributeBuilder.build(GoogleAppsConnector.ATTR_FAMILY_NAME,
familyName));
builder.addAttribute(AttributeBuilder.build(GoogleAppsConnector.ATTR_GIVEN_NAME, givenName));
builder.addAttribute(AttributeBuilder.buildEnabled(!suspended));
// if either nicknames or groups are empty lists, return them anyway
builder.addAttribute(AttributeBuilder.build(GoogleAppsConnector.ATTR_NICKNAME_LIST,
nicknames));
builder.addAttribute(AttributeBuilder.build(PredefinedAttributes.GROUPS_NAME, groups));
Quota quota = ue.getQuota();
if (quota != null) {
builder.addAttribute(AttributeBuilder.build(GoogleAppsConnector.ATTR_QUOTA, quota
.getLimit()));
}
log.info("Make object uid={0} fn={1} gn={2} nicks={3} groups={4}", uid, familyName,
givenName, nicknames, groups);
return builder.build();
}
private final List<String> emptyList = new ArrayList<String>(1);
/**
* Retrive the user with the given uid
*
* @param id
* - the user id for the account
* @param fetchNicknames
* - if true we should also fetch associated nicknames. This can
* be expensive
* @return The user object if it exists, null otherwise
*/
private ConnectorObject get(String id, boolean fetchNicknames, boolean fetchGroups) {
UserEntry ue = null;
GoogleAppsClient g = gc.getClient();
log.info("Fetching google apps user {0}", id);
ue = g.getUserEntry(id);
log.info("UserEntry = {0}", GoogleAppsClient.userEntrytoString(ue));
if (ue != null) {
List<String> nicks = (fetchNicknames ? g.getNicknamesAsList(id) : emptyList);
List<String> groups = (fetchGroups ? g.getGroupMembershipsForUser(id) : emptyList);
return makeConnectorObject(ue, nicks, groups);
}
return null;
}
/**
* Update the user entry - including associated nicknames (aliases)
*
* @param uid
* @param replaceAttrs
* @param options
* @return
*/
Uid updateUser(Uid uid, Set<Attribute> replaceAttrs, OperationOptions options) {
AttributesAccessor a = new AttributesAccessor(replaceAttrs);
final String accountId = uid.getUidValue();
final GoogleAppsClient g = gc.getClient();
// this an optimization in case the update only includes nicknames
// we can skip the update on the other attributes
boolean justDoNicknames =
replaceAttrs.size() == 1
&& a.findStringList(GoogleAppsConnector.ATTR_NICKNAME_LIST) != null;
if (!justDoNicknames) {
// fetch the complete object so we can merge in the changes
final UserEntry userEntry = g.getUserEntry(accountId);
if (userEntry == null) {
throw new ConnectorException(
"Update failed. Could not read current state for user " + accountId);
}
// set the updated values
log.info("Rerieved UserEntry {0}", GoogleAppsClient.userEntrytoString(userEntry));
String password = getPlainPassword(a.getPassword());
final String first = a.findString(GoogleAppsConnector.ATTR_FAMILY_NAME);
final String given = a.findString(GoogleAppsConnector.ATTR_GIVEN_NAME);
// note google apps uses suspend instead of enable - so we have to
// negate the result
final boolean suspended = !a.getEnabled(true);
// merge in replace attributes
UserEntry ue =
g.setUserEntry(userEntry, accountId, password, given, first, suspended, null);
// update the user. Throws an exception if it fails.
ue = g.updateUser(accountId, ue);
}
// Update the nicknames
List<String> nicknamesToUpdate = a.findStringList(GoogleAppsConnector.ATTR_NICKNAME_LIST);
if (nicknamesToUpdate != null) {
// we don't get a list of the deltas - so we need to
// read the current list to compare what we need to add/delete
// google apps does not have a "delete all" nicknames request -
// so we don't have the option to delete all and then add in the
// update list
List<String> currentNames = g.getNicknamesAsList(accountId);
log.info("Existing nickname for account {0} are: {1}", accountId, currentNames);
// we don't get a list of the deltas - so we need to
// read the current list to compare what we need to add/delete
// google apps does not have a "delete all" nicknames request -
// so we don't have the option to delete all and then add in the
// update list
// ChangeSet changes = new ChangeSet(currentNames,
// nicknamesToUpdate);
new ChangeSetExecutor(currentNames, nicknamesToUpdate) {
@Override
public void doAdd(String nickname) {
log.info("Adding nickname ${0} to user {1}", nickname, accountId);
g.createNickname(accountId, nickname);
}
@Override
public void doRemove(String nickname) {
log.info("Removing nickname {0}", nickname);
g.deleteNickname(nickname);
}
}.execute();
}
// update group membership
List<String> groupsToUpdate = a.findStringList(PredefinedAttributes.GROUPS_NAME);
if (groupsToUpdate != null) {
// we don't get a list of the deltas - so we need to
// read the current list to compare what we need to add/delete
// google apps does not have a "delete all" nicknames request -
// so we don't have the option to delete all and then add in the
// update list
List<String> currentGroups = g.getGroupMembershipsForUser(accountId);
log.info("Existing groups for account {0} are: {1}", accountId, currentGroups);
new ChangeSetExecutor(currentGroups, groupsToUpdate) {
@Override
public void doAdd(String group) {
log.info("Adding group ${0} to user {1}", group, accountId);
g.addGroupMember(group, accountId);
}
@Override
public void doRemove(String group) {
log.info("Removing user from group {0}", group);
g.removeGroupMember(group, accountId);
}
}.execute();
}
return new Uid((accountId));
}
}