/*
* 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.security.providers;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.usergrid.management.ManagementService;
import org.apache.usergrid.persistence.EntityManager;
import org.apache.usergrid.persistence.index.query.Identifier;
import org.apache.usergrid.persistence.Query;
import org.apache.usergrid.persistence.Results;
import org.apache.usergrid.persistence.entities.User;
import org.apache.usergrid.security.tokens.exceptions.BadTokenException;
import org.apache.usergrid.utils.JsonUtils;
import static org.apache.usergrid.persistence.Schema.PROPERTY_MODIFIED;
import static org.apache.usergrid.utils.ListUtils.anyNull;
/**
* Provider implementation for sign-in-as with facebook
*
* @author zznate
*/
public class FacebookProvider extends AbstractProvider {
private static final String DEF_API_URL = "https://graph.facebook.com/me";
private static final String DEF_PICTURE_URL = "http://graph.facebook.com/%s/picture";
private static final Logger logger = LoggerFactory.getLogger( FacebookProvider.class );
private String apiUrl = DEF_API_URL;
private String pictureUrl = DEF_PICTURE_URL;
FacebookProvider( EntityManager entityManager, ManagementService managementService ) {
super( entityManager, managementService );
}
@Override
void configure() {
try {
Map config = loadConfigurationFor( "facebookProvider" );
if ( config != null ) {
String foundApiUrl = ( String ) config.get( "api_url" );
if ( foundApiUrl != null ) {
apiUrl = foundApiUrl;
}
String foundPicUrl = ( String ) config.get( "pic_url" );
if ( foundPicUrl != null ) {
pictureUrl = foundApiUrl;
}
}
}
catch ( Exception ex ) {
logger.error("Error in configure()", ex);
}
}
@Override
public Map<Object, Object> loadConfigurationFor() {
return loadConfigurationFor( "facebookProvider" );
}
/** Configuration parameters we look for: <ul> <li>api_url</li> <li>pic_url</li> </ul> */
@Override
public void saveToConfiguration( Map<String, Object> config ) {
saveToConfiguration( "facebookProvider", config );
}
@Override
Map<String, Object> userFromResource( String externalToken ) {
return client.target( apiUrl )
.queryParam( "access_token", externalToken )
.request()
.accept( MediaType.APPLICATION_JSON )
.get( Map.class );
}
@Override
public User createOrAuthenticate( String externalToken ) throws BadTokenException {
Map<String, Object> fb_user = userFromResource( externalToken );
String fb_user_id = ( String ) fb_user.get( "id" );
String fb_user_name = ( String ) fb_user.get( "name" );
String fb_user_username = ( String ) fb_user.get( "username" );
String fb_user_email = ( String ) fb_user.get( "email" );
if ( logger.isDebugEnabled() ) {
logger.debug( "FacebookProvider.createOrAuthenticate: {}", JsonUtils.mapToFormattedJsonString( fb_user ) );
}
User user = null;
if ( ( fb_user != null ) && !anyNull( fb_user_id, fb_user_name ) ) {
Results r = null;
try {
final Query query = Query.fromEquals( "facebook.id", fb_user_id );
r = entityManager.searchCollection( entityManager.getApplicationRef(), "users", query );
}
catch ( Exception ex ) {
throw new BadTokenException( "Could not lookup user for that Facebook ID", ex );
}
if ( r.size() > 1 ) {
logger.error( "Multiple users for FB ID: {}", fb_user_id );
throw new BadTokenException( "multiple users with same Facebook ID" );
}
if ( r.size() < 1 ) {
Map<String, Object> properties = new LinkedHashMap<String, Object>();
properties.put( "facebook", fb_user );
properties.put( "username", "fb_" + fb_user_id );
properties.put( "name", fb_user_name );
properties.put( "picture", String.format( pictureUrl, fb_user_id ) );
if ( fb_user_email != null ) {
try {
user = managementService.getAppUserByIdentifier( entityManager.getApplication().getUuid(),
Identifier.fromEmail( fb_user_email ) );
}
catch ( Exception ex ) {
throw new BadTokenException(
"Could not find existing user for this applicaiton for email: " + fb_user_email, ex );
}
// if we found the user by email, unbind the properties from above
// that will conflict
// then update the user
if ( user != null ) {
properties.remove( "username" );
properties.remove( "name" );
try {
entityManager.updateProperties( user, properties );
}
catch ( Exception ex ) {
throw new BadTokenException( "Could not update user with new credentials", ex );
}
user.setProperty( PROPERTY_MODIFIED, properties.get( PROPERTY_MODIFIED ) );
}
else {
properties.put( "email", fb_user_email );
}
}
if ( user == null ) {
properties.put( "activated", true );
try {
user = entityManager.create( "user", User.class, properties );
}
catch ( Exception ex ) {
throw new BadTokenException( "Could not create user for that token", ex );
}
}
}
else {
user = ( User ) r.getEntity().toTypedEntity();
Map<String, Object> properties = new LinkedHashMap<String, Object>();
properties.put( "facebook", fb_user );
properties.put( "picture", String.format( pictureUrl, fb_user_id ) );
try {
entityManager.updateProperties( user, properties );
user.setProperty( PROPERTY_MODIFIED, properties.get( PROPERTY_MODIFIED ) );
user.setProperty( "facebook", fb_user );
user.setProperty( "picture", String.format( pictureUrl, fb_user_id ) );
}
catch ( Exception ex ) {
throw new BadTokenException( "Could not update user properties", ex );
}
}
}
else {
throw new BadTokenException( "Unable to confirm Facebook access token" );
}
return user;
}
}