/*
  Copyright 2012 Jerome Leleu

   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.
 */
package com.github.leleuj.ss.oauth.client.authentication;

import java.util.ArrayList;
import java.util.Collection;

import org.scribe.up.credential.OAuthCredential;
import org.scribe.up.profile.UserProfile;
import org.scribe.up.provider.OAuthProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.util.Assert;

/**
 * This provider authenticates OAuth credentials stored in ({@link com.github.leleuj.ss.oauth.client.authentication.OAuthAuthenticationToken})
 * to get the user profile and finally the user details (and authorities).
 * 
 * @author Jerome Leleu
 * @since 1.0.0
 */
public final class OAuthAuthenticationProvider implements AuthenticationProvider,
    InitializingBean {
    
    private static final Logger logger = LoggerFactory
        .getLogger(OAuthAuthenticationProvider.class);
    
    private OAuthProvider provider = null;
    
    private AuthenticationUserDetailsService<OAuthAuthenticationToken> oauthUserDetailsService = null;
    
    private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
    
    public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
        logger.debug("authentication : {}", authentication);
        if (!supports(authentication.getClass())) {
            logger.debug("unsupported authentication class : {}", authentication.getClass());
            return null;
        }
        
		if (authentication.getCredentials() == null) {
			logger.debug("authentication failed");
			return null;
		}
        
        // get the OAuth credentials
        OAuthCredential credential = (OAuthCredential) authentication.getCredentials();
        logger.debug("credential : {}", credential);
        
        // check it is the right provider for the right credential
        if (!provider.getType().equals(credential.getProviderType())) {
            logger.debug("unsupported provider type, expected : {} / returned : {}", provider.getType(), credential.getProviderType());
            return null;
        }
        
        // get the user profile
        UserProfile userProfile = provider.getUserProfile(credential);
        logger.debug("userProfile : {}", userProfile);
        
        // by default, no authorities
        Collection<? extends GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        // get user details and check them
        if (oauthUserDetailsService != null) {
            OAuthAuthenticationToken tmpToken = new OAuthAuthenticationToken(credential, userProfile, null);
            UserDetails userDetails = oauthUserDetailsService.loadUserDetails(tmpToken);
            logger.debug("userDetails : {}", userDetails);
            userDetailsChecker.check(userDetails);
            authorities = userDetails.getAuthorities();
            logger.debug("authorities : {}", authorities);
        }
        
        // new token with credential (like previously) and user profile and authorities
        OAuthAuthenticationToken result = new OAuthAuthenticationToken(credential, userProfile, authorities);
        result.setDetails(authentication.getDetails());
        logger.debug("result : {}", result);
        return result;
    }
    
    public boolean supports(Class<?> authentication) {
        return (OAuthAuthenticationToken.class.isAssignableFrom(authentication));
    }
    
    public void afterPropertiesSet() {
        Assert.notNull(provider, "provider cannot be null");
    }
    
    public void setProvider(OAuthProvider provider) {
        this.provider = provider;
    }
    
    public void setOauthUserDetailsService(AuthenticationUserDetailsService<OAuthAuthenticationToken> oauthUserDetailsService) {
        this.oauthUserDetailsService = oauthUserDetailsService;
    }
    
    public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) {
        this.userDetailsChecker = userDetailsChecker;
    }
}

@@ -1623,16 +1623,20 @@
 red in (
+%0A * 
 %7B@link c
@@ -1709,19 +1709,16 @@
 nToken%7D)
-%0A *
  to get 
@@ -1762,16 +1762,19 @@
  details
+%0A *
  (and au
@@ -1914,20 +1914,16 @@
 rovider,
-%0A   
  Initial
@@ -1993,25 +1993,16 @@
 rFactory
-%0A        
 .getLogg
@@ -2336,16 +2336,22 @@
 nticate(
+final 
 Authenti
@@ -2372,24 +2372,16 @@
 ication)
-%0A       
  throws 
@@ -2661,128 +2661,8 @@
    %0A
-%09%09if (authentication.getCredentials() == null) %7B%0A%09%09%09logger.debug(%22authentication failed%22);%0A%09%09%09return null;%0A%09%09%7D%0A        %0A
     
@@ -2698,24 +2698,30 @@
 als%0A        
+final 
 OAuthCredent
@@ -2833,32 +2833,152 @@
 %22, credential);%0A
+        if (credential == null) %7B%0A            logger.debug(%22authentication failed%22);%0A            return null;%0A        %7D%0A
         %0A       
@@ -3050,16 +3050,21 @@
    if (!
+this.
 provider
@@ -3199,16 +3199,21 @@
  : %7B%7D%22, 
+this.
 provider
@@ -3223,16 +3223,41 @@
 tType(),
+%0A                        
  credent
@@ -3360,24 +3360,30 @@
 ile%0A        
+final 
 UserProfile 
@@ -3396,16 +3396,21 @@
 ofile = 
+this.
 provider
@@ -3691,16 +3691,21 @@
     if (
+this.
 oauthUse
@@ -3735,32 +3735,38 @@
 ) %7B%0A            
+final 
 OAuthAuthenticat
@@ -3858,16 +3858,22 @@
         
+final 
 UserDeta
@@ -3890,16 +3890,21 @@
 tails = 
+this.
 oauthUse
@@ -4017,16 +4017,21 @@
         
+this.
 userDeta
@@ -4291,16 +4291,22 @@
         
+final 
 OAuthAut
@@ -4562,16 +4562,22 @@
 upports(
+final 
 Class%3C?%3E
@@ -4750,16 +4750,21 @@
 notNull(
+this.
 provider
@@ -4832,16 +4832,22 @@
 rovider(
+final 
 OAuthPro
@@ -4952,16 +4952,22 @@
 Service(
+final 
 Authenti
@@ -5157,16 +5157,22 @@
 Checker(
+final 
 UserDeta

move test on credential == null