package com.sixsq.slipstream.user;
/*
* +=================================================================+
* SlipStream Server (WAR)
* =====
* Copyright (C) 2013 SixSq Sarl (sixsq.com)
* =====
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* -=================================================================-
*/
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.sixsq.slipstream.event.ACL;
import com.sixsq.slipstream.event.Event;
import com.sixsq.slipstream.event.TypePrincipal;
import com.sixsq.slipstream.event.TypePrincipalRight;
import org.restlet.data.Cookie;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.resource.*;
import com.sixsq.slipstream.configuration.Configuration;
import com.sixsq.slipstream.connector.Connector;
import com.sixsq.slipstream.connector.ConnectorBase;
import com.sixsq.slipstream.connector.ConnectorFactory;
import com.sixsq.slipstream.connector.ExecutionControlUserParametersFactory;
import com.sixsq.slipstream.connector.UserParametersFactoryBase;
import com.sixsq.slipstream.cookie.CookieUtils;
import com.sixsq.slipstream.exceptions.BadlyFormedElementException;
import com.sixsq.slipstream.exceptions.ConfigurationException;
import com.sixsq.slipstream.exceptions.InvalidElementException;
import com.sixsq.slipstream.exceptions.SlipStreamClientException;
import com.sixsq.slipstream.exceptions.ValidationException;
import com.sixsq.slipstream.factory.ParametersFactory;
import com.sixsq.slipstream.persistence.Parameter;
import com.sixsq.slipstream.persistence.ParameterCategory;
import com.sixsq.slipstream.persistence.ServiceConfiguration;
import com.sixsq.slipstream.persistence.ServiceConfigurationParameter;
import com.sixsq.slipstream.persistence.User;
import com.sixsq.slipstream.persistence.User.State;
import com.sixsq.slipstream.persistence.UserParameter;
import com.sixsq.slipstream.resource.ParameterizedResource;
import com.sixsq.slipstream.util.FileUtil;
import com.sixsq.slipstream.util.ModuleUriUtil;
import com.sixsq.slipstream.util.SerializationUtil;
import com.sixsq.slipstream.util.XmlUtil;
import static com.sixsq.slipstream.event.TypePrincipal.PrincipalType.ROLE;
import static com.sixsq.slipstream.event.TypePrincipal.PrincipalType.USER;
import static com.sixsq.slipstream.event.TypePrincipalRight.Right.ALL;
/**
* @see UserResourceTest
*/
public class UserResource extends ParameterizedResource<User> {
public static final String USERNAME_URI_ATTRIBUTE = "user";
private static final String resourceRoot = User.RESOURCE_URL_PREFIX;
@Get("txt")
@Override
public Representation toTxt() {
return super.toTxt();
}
@Get("xml")
public Representation toXml() {
User user = (User) getParameterized();
try {
mergeCloudSystemParameters(user);
mergeCloudConnectorParameters(user);
replaceUserPublicSshKeyWithServerSshPublicKeyIfEmpty(user);
} catch (ConfigurationException e) {
throwConfigurationException(e);
} catch (ValidationException e) {
throwClientValidationError(e.getMessage());
}
return super.toXml();
}
@Override
protected boolean isMachineAllowedToAccessThisResource(){
return true;
}
private void replaceUserPublicSshKeyWithServerSshPublicKeyIfEmpty(User user)
throws ConfigurationException, ValidationException {
Cookie cookie = CookieUtils.extractAuthnCookie(getRequest());
if (CookieUtils.isMachine(cookie) == true) {
UserParameter userPublicSshKey = user.getParameter(ExecutionControlUserParametersFactory.CATEGORY + "."
+ UserParametersFactoryBase.SSHKEY_PARAMETER_NAME);
if (userPublicSshKey != null && userPublicSshKey.getValue("").trim().isEmpty()) {
String serverPublicSshKey = FileUtil.fileToString(ConnectorBase.getServerPublicSshKeyFilename());
userPublicSshKey.setValue(serverPublicSshKey);
user.getParameters().put(userPublicSshKey.getName(), userPublicSshKey);
}
}
}
private void mergeCloudSystemParameters(User user) throws ConfigurationException, ValidationException {
Cookie cookie = CookieUtils.extractAuthnCookie(getRequest());
String cloudServiceName = CookieUtils.getCookieCloudServiceName(cookie);
if (cloudServiceName != null) {
for (Entry<String, Parameter<ServiceConfiguration>> p : Configuration.getInstance().getParameters()
.getParameters(cloudServiceName).entrySet()) {
user.getParameters().put(p.getKey(), UserParameter.convert(p.getValue()));
}
}
mergePublicKeyParameter(user);
}
private void mergeCloudConnectorParameters(User user) throws ConfigurationException, ValidationException {
Cookie cookie = CookieUtils.extractAuthnCookie(getRequest());
String cloudServiceName = CookieUtils.getCookieCloudServiceName(cookie);
if (cloudServiceName != null && CookieUtils.isMachine(cookie) == true) {
Connector connector = ConnectorFactory.getConnector(cloudServiceName);
connector.setExtraUserParameters(user);
}
}
@Override
protected User prepareForSerialization() throws ConfigurationException, ValidationException {
User user = getParameterized();
Cookie cookie = CookieUtils.extractAuthnCookie(getRequest());
String cloudServiceName = CookieUtils.getCookieCloudServiceName(cookie);
if (cloudServiceName != null && CookieUtils.isMachine(cookie) == true) {
Map<String, Parameter<User>> params = user.getParameters(ParameterCategory.General.name());
params.putAll(user.getParameters(cloudServiceName));
Map<String, UserParameter> userParameters = user.getParameters();
userParameters.clear();
for (Map.Entry<String,Parameter<User>> entry : params.entrySet()) {
userParameters.put(entry.getKey(), (UserParameter)entry.getValue());
}
}
return user;
}
private void mergePublicKeyParameter(User user)
throws ConfigurationException, ValidationException {
String pubKeyParameterName = ServiceConfiguration.RequiredParameters.CLOUD_CONNECTOR_ORCHESTRATOR_PUBLICSSHKEY
.getName();
ServiceConfigurationParameter pubKeySystemParameter = Configuration
.getInstance().getParameters()
.getParameter(pubKeyParameterName);
String pubKeyFilePath = pubKeySystemParameter.getValue();
if (FileUtil.exist(pubKeyFilePath)) {
String pubKey = FileUtil.fileToString(pubKeyFilePath);
pubKeyParameterName = "General.orchestrator.publicsshkey";
UserParameter pubKeyUserParameter = new UserParameter(
pubKeyParameterName, pubKey, "");
pubKeyUserParameter.setCategory("General");
user.getParameters().put(pubKeyParameterName, pubKeyUserParameter);
}
}
@Get("html")
public Representation toHtml() {
return super.toHtml();
}
protected String getPageRepresentation() {
return "user";
}
@Override
protected String extractTargetUriFromRequest() {
return (String) getRequest().getAttributes().get(USERNAME_URI_ATTRIBUTE);
}
@Override
protected User getOrCreateParameterized(String name) {
User user = getParameterized();
if (user == null) {
try {
user = new User(name);
} catch (ValidationException e) {
throw (new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST,
e.getMessage()));
}
}
return user;
}
@Override
protected void authorize() {
boolean isMachine = isMachine();
String targetUserOrganization = null;
try {
targetUserOrganization = getTargetUser().getOrganization();
} catch (Exception e) {}
String organizationManagedByUser = getUser().getOrganizationManagedForUserCreator();
boolean isOrganizationManagedByUser = false;
if (isExisting() && targetUserOrganization != null && organizationManagedByUser != null) {
isOrganizationManagedByUser = targetUserOrganization.equals(organizationManagedByUser);
}
setCanPut(!newTemplateResource() && !isMachine
&& (getUser().isSuper() || !isExisting()
|| (newInQuery() && !isExisting()) || isItSelf() || isOrganizationManagedByUser));
setCanDelete((getUser().isSuper() || isItSelf() || isOrganizationManagedByUser) && !isMachine);
setCanGet(getUser().isSuper() || newTemplateResource() || isItSelf() || isOrganizationManagedByUser);
}
protected boolean newInQuery() {
return extractNewFlagFromQuery();
}
protected boolean newTemplateResource() {
return isExisting()
&& NEW_NAME.equals(ModuleUriUtil
.extractShortNameFromResourceUri(getParameterized()
.getName()));
}
private boolean isItSelf() {
return isExisting()
&& getUser().getResourceUri().equals(
getTargetUser().getResourceUri());
}
private User getTargetUser() {
return (User) getParameterized();
}
@Override
protected void addParametersForEditing() throws ValidationException,
ConfigurationException {
ParametersFactory.addParametersForEditing(getParameterized());
}
@Put("form")
public void updateOrCreateFromForm(Representation entity)
throws ResourceException {
if (!canPut()) {
throwClientForbiddenError();
}
if (!isExisting()) {
setParameterized(getOrCreateParameterized(getTargetParameterizeUri()));
}
try {
addParametersForEditing();
} catch (ValidationException e) {
throwClientValidationError(e.getMessage());
} catch (ConfigurationException e) {
throwConfigurationException(e);
}
processEntityAsForm(entity);
try {
updateOrCreate(getParameterized());
} catch (ValidationException e) {
throwClientBadRequest(e.getMessage());
}
setEmptyEntity(MediaType.APPLICATION_WWW_FORM);
}
@Put("xml")
public void updateOrCreateFromXml(Representation entity)
throws ResourceException {
User user = xmlToUser();
try {
ParametersFactory.addParametersForEditing(user);
} catch (ValidationException e) {
throwClientValidationError(e.getMessage());
} catch (ConfigurationException e) {
throwConfigurationException(e);
}
if (!getUser().isSuper()) {
if (user.isSuper()) {
throwClientForbiddenError("Only super users are authorized to create a privileged user!");
}
if (user.getRoles() != null) {
throwClientForbiddenError("Only super users are authorized to update roles!");
}
}
try {
updateOrCreate(user);
} catch (ValidationException e) {
throwClientValidationError(e.getMessage());
}
if (isExisting()) {
getResponse().setStatus(Status.SUCCESS_ACCEPTED);
} else {
getResponse().setStatus(Status.SUCCESS_CREATED);
}
setEmptyEntity(MediaType.APPLICATION_XML);
}
private User xmlToUser() {
return xmlToUser(extractXml());
}
public static User xmlToUser(String xml) {
String denormalized = XmlUtil.denormalize(xml);
User user = null;
try {
user = (User) SerializationUtil.fromXml(denormalized, User.class);
} catch (SlipStreamClientException e) {
throwClientBadRequest("Invalid xml user: " + e.getMessage());
}
user.postDeserialization();
return user;
}
private String extractXml() {
return getRequest().getEntityAsText();
}
private void updateOrCreate(User user) throws ValidationException {
checkCanPut();
try {
user.validate();
} catch (ValidationException ex) {
throw new ResourceException(Status.CLIENT_ERROR_CONFLICT, ex.getMessage());
}
User existingUser = getParameterized();
user.setRolesFromUserIfNull(existingUser);
user.setPasswordFromUserIfNull(existingUser);
try {
User.validateMinimumInfo(user);
} catch (InvalidElementException ex) {
throw new ResourceException(Status.CLIENT_ERROR_CONFLICT, ex.getMessage());
}
if (!getTargetParameterizeUri().equals(user.getName())
&& !getUser().isSuper()) {
throwClientBadRequest("The uploaded user does not correspond to the target user uri");
}
// super users forces new users to become active
if (!isExisting() && getUser().isSuper()) {
State current = user.getState();
State newState = current == State.NEW ? State.ACTIVE : current;
user.setState(newState);
}
user = user.store();
if (!isExisting()) {
setParameterized(user);
postEventCreated(user);
} else {
postEventUpdated(user);
}
setResponseForPut();
}
private void setResponseForPut() {
if (isExisting()) {
setResponseOkAndViewLocation(getParameterized().getResourceUri());
} else {
setResponseCreatedAndViewLocation(getParameterized().getResourceUri());
}
}
private void processEntityAsForm(Representation entity) {
Form form = extractForm(entity);
processForm(form);
}
private Form extractForm(Representation entity) {
Form form = null;
try {
String text = entity.getText();
form = new Form(text);
} catch (IOException e) {
throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST,
"cannot extract text from entity");
}
return form;
}
private void processForm(Form form) {
UserFormProcessor processor = new UserFormProcessor(getParameterized(),
getUser());
try {
processor.processForm(form);
} catch (BadlyFormedElementException e) {
throwClientError(e);
} catch (SlipStreamClientException e) {
throwClientError(e);
} catch (IllegalArgumentException e) {
throwClientError(e);
}
}
public static String getResourceRoot() {
return resourceRoot;
}
@Override
protected User loadParameterized(String targetParameterizedName)
throws ConfigurationException, ValidationException {
return User.loadByName(targetParameterizedName);
}
@Delete
@Override
public void deleteResource() {
if (!canDelete()) {
throwClientForbiddenError();
}
getTargetUser().setState(State.DELETED);
getTargetUser().store();
postEventDeleted(getParameterized());
}
public static void postEventRegistered(User user) {
postEventUser(Event.Severity.medium, "User '" + user.getName() + "' has registered", user.getName(), "system");
}
public static void postEventValidated(User user) {
postEventUser(Event.Severity.medium, "User '" + user.getName() + "' has validated his account", user.getName(),
"system");
}
public static void postEventPasswordReseted(User user) {
postEventUser(Event.Severity.medium, "User '" + user.getName() + "' has reseted his password", user.getName(),
"system");
}
private void postEventCreated(User user) {
postEventUserAction(Event.Severity.medium, "created", user);
}
private void postEventUpdated(User user) {
postEventUserAction(Event.Severity.medium, "updated", user);
}
private void postEventDeleted(User user) {
postEventUserAction(Event.Severity.high, "deleted", user);
}
private void postEventUserAction(Event.Severity severity, String action, User user) {
String message = "User '" + user.getName() + "' " + action + " by '" + getUser().getName() + "'";
postEventUser(severity, message, user.getName());
}
private void postEventUser(Event.Severity severity, String message, String username) {
postEventUser(severity, message, username, getUser().getName());
}
private static void postEventUser(Event.Severity severity, String message, String username, String ownerUsername) {
String resourceRef = getResourceRoot() + username;
Event.postEvent(resourceRef, severity, message, ownerUsername, Event.EventType.action);
}
}