/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.usergrid.rest.applications.users;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.json.annotation.JSONP;
import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
import org.apache.usergrid.persistence.Entity;
import org.apache.usergrid.persistence.Query;
import org.apache.usergrid.persistence.entities.User;
import org.apache.usergrid.persistence.index.query.Identifier;
import org.apache.usergrid.rest.AbstractContextResource;
import org.apache.usergrid.rest.ApiResponse;
import org.apache.usergrid.rest.RootResource;
import org.apache.usergrid.rest.applications.ServiceResource;
import org.apache.usergrid.rest.exceptions.RedirectionException;
import org.apache.usergrid.rest.security.annotations.CheckPermissionsForPath;
import org.apache.usergrid.rest.security.annotations.RequireApplicationAccess;
import org.glassfish.jersey.server.mvc.Viewable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.UriInfo;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.usergrid.services.ServiceParameter.addParameter;
@Component("org.apache.usergrid.rest.applications.users.UsersResource")
@Scope("prototype")
@Produces(MediaType.APPLICATION_JSON)
public class UsersResource extends ServiceResource {
private static final Logger logger = LoggerFactory.getLogger( UsersResource.class );
String errorMsg;
User user;
public UsersResource() {
}
@Override
@Path(RootResource.ENTITY_ID_PATH)
public AbstractContextResource addIdParameter( @Context UriInfo ui, @PathParam("entityId") PathSegment entityId )
throws Exception {
if(logger.isTraceEnabled()){
logger.trace( "ServiceResource.addIdParameter" );
}
UUID itemId = UUID.fromString( entityId.getPath() );
addParameter( getServiceParameters(), itemId );
addMatrixParams( getServiceParameters(), ui, entityId );
return getSubResource( UserResource.class ).init( Identifier.fromUUID( itemId ) );
}
@Override
@Path("{itemName}")
public AbstractContextResource addNameParameter( @Context UriInfo ui, @PathParam("itemName") PathSegment itemName)
throws Exception {
if(logger.isTraceEnabled()){
logger.trace( "ServiceResource.addNameParameter" );
logger.trace( "Current segment is {}", itemName.getPath() );
}
if ( itemName.getPath().startsWith( "{" ) ) {
Query query = Query.fromJsonString( itemName.getPath() );
if ( query != null ) {
addParameter( getServiceParameters(), query );
}
addMatrixParams( getServiceParameters(), ui, itemName );
return getSubResource( ServiceResource.class );
}
addParameter( getServiceParameters(), itemName.getPath() );
addMatrixParams( getServiceParameters(), ui, itemName );
String forceString = ui.getQueryParameters().getFirst("force");
Identifier id;
if (forceString != null && "email".equals(forceString.toLowerCase())) {
id = Identifier.fromEmail(itemName.getPath().toLowerCase());
} else if (forceString != null && "name".equals(forceString.toLowerCase())) {
id = Identifier.fromName(itemName.getPath().toLowerCase());
} else {
id = Identifier.from(itemName.getPath());
}
if ( id == null ) {
throw new IllegalArgumentException( "Not a valid user identifier: " + itemName.getPath() );
}
return getSubResource( UserResource.class ).init( id );
}
@GET
@Path("resetpw")
@Produces(MediaType.TEXT_HTML)
public Viewable showPasswordResetForm( @Context UriInfo ui ) {
return handleViewable( "resetpw_email_form", this, getOrganizationName() );
}
@POST
@Path("resetpw")
@Consumes("application/x-www-form-urlencoded")
@Produces(MediaType.TEXT_HTML)
public Viewable handlePasswordResetForm( @Context UriInfo ui, @FormParam("email") String email,
@FormParam("recaptcha_challenge_field") String challenge,
@FormParam("recaptcha_response_field") String uresponse ) {
try {
ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
reCaptcha.setPrivateKey( properties.getRecaptchaPrivate() );
ReCaptchaResponse reCaptchaResponse =
reCaptcha.checkAnswer( httpServletRequest.getRemoteAddr(), challenge, uresponse );
if ( isBlank( email ) ) {
errorMsg = "No email provided, try again...";
return handleViewable( "resetpw_email_form", this, getOrganizationName() );
}
if ( !useReCaptcha() || reCaptchaResponse.isValid() ) {
user = management.getAppUserByIdentifier( getApplicationId(), Identifier.fromEmail( email ) );
if ( user != null ) {
management.startAppUserPasswordResetFlow( getApplicationId(), user );
return handleViewable( "resetpw_email_success", this, getOrganizationName() );
}
else {
errorMsg = "We don't recognize that email, try again...";
return handleViewable( "resetpw_email_form", this, getOrganizationName() );
}
}
else {
errorMsg = "Incorrect Captcha, try again...";
return handleViewable( "resetpw_email_form", this, getOrganizationName() );
}
}
catch ( RedirectionException e ) {
throw e;
}
catch ( Exception e ) {
return handleViewable( "resetpw_email_form", e, getOrganizationName() );
}
}
public String getErrorMsg() {
return errorMsg;
}
public User getUser() {
return user;
}
@PUT
@Override
@RequireApplicationAccess
@JSONP
@Produces({MediaType.APPLICATION_JSON, "application/javascript"})
public ApiResponse executePut( @Context UriInfo ui, String body,
@QueryParam("callback") @DefaultValue("callback") String callback )
throws Exception {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> json = mapper.readValue( body, mapTypeReference );
if ( "me".equals( json.get("username") ) ) {
throw new IllegalArgumentException( "Username 'me' is reserved" );
}
User user = getUser();
if ( user == null ) {
return executePost( ui, body, callback );
}
if ( json != null ) {
json.remove( "password" );
json.remove( "pin" );
}
return super.executePutWithMap( ui, json, callback );
}
@CheckPermissionsForPath
@POST
@Override
@JSONP
@Produces({MediaType.APPLICATION_JSON, "application/javascript"})
public ApiResponse executePost( @Context UriInfo ui, String body,
@QueryParam("callback") @DefaultValue("callback") String callback )
throws Exception {
if(logger.isTraceEnabled()){
logger.trace( "UsersResource.executePost: body = {}", body);
}
Object json = readJsonToObject( body );
String password = null;
String pin = null;
Boolean confRequred = (Boolean)this.getServices().getEntityManager().getProperty(
this.getServices().getApplicationRef(), "registration_requires_email_confirmation" );
boolean activated = !( ( confRequred != null ) && confRequred );
if(logger.isTraceEnabled()){
logger.trace("Confirmation required: {} Activated: {}", confRequred, activated );
}
if ( json instanceof Map ) {
@SuppressWarnings("unchecked") Map<String, Object> map = ( Map<String, Object> ) json;
if ( "me".equals( map.get("username") ) ) {
throw new IllegalArgumentException( "Username 'me' is reserved" );
}
password = ( String ) map.get( "password" );
map.remove( "password" );
pin = ( String ) map.get( "pin" );
map.remove( "pin" );
map.put( "activated", activated );
}
else if ( json instanceof List ) {
@SuppressWarnings("unchecked") List<Object> list = ( List<Object> ) json;
for ( Object obj : list ) {
if ( obj instanceof Map ) {
@SuppressWarnings("unchecked") Map<String, Object> map = ( Map<String, Object> ) obj;
map.remove( "password" );
map.remove( "pin" );
}
}
}
ApiResponse response = super.executePostWithObject( ui, json, callback );
if ( ( response.getEntities() != null ) && ( response.getEntities().size() == 1 ) ) {
Entity entity = response.getEntities().get( 0 );
User user = ( User ) entity.toTypedEntity();
if ( isNotBlank( password ) ) {
management.setAppUserPassword( getApplicationId(), user.getUuid(), password );
}
if ( isNotBlank( pin ) ) {
management.setAppUserPin( getApplicationId(), user.getUuid(), pin );
}
if ( !activated ) {
management.startAppUserActivationFlow( getApplicationId(), user );
}
}
return response;
}
}