/***************************** BEGIN LICENSE BLOCK ***************************

The contents of this file are subject to the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
 
Copyright (C) 2012-2015 Sensia Software LLC. All Rights Reserved.
 
******************************* END LICENSE BLOCK ***************************/

package org.sensorhub.impl.security.oauth;

import java.io.IOException;
import java.io.StringReader;
import java.util.UUID;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.codec.binary.Base64;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthAuthzResponse;
import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
import org.apache.oltu.oauth2.client.response.OAuthResourceResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.OAuth.HttpMethod;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.security.authentication.SessionAuthentication;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.UserIdentity;
import org.slf4j.Logger;
import com.google.gson.stream.JsonReader;


public class OAuthAuthenticator extends LoginAuthenticator
{
    private static final String AUTH_METHOD_OAUTH2 = "OAUTH2";
    
    private Logger log;
    private OAuthClientConfig config;
    private String generatedState;
    

    public OAuthAuthenticator(OAuthClientConfig config, Logger log)
    {
        this.log = log;
        this.config = config;
    }
    
    
    @Override
    public String getAuthMethod()
    {
        return AUTH_METHOD_OAUTH2;
    }


    @Override
    public void prepareRequest(ServletRequest arg0)
    {
        // nothing to prepare
    }


    @Override
    public boolean secureResponse(ServletRequest arg0, ServletResponse arg1, boolean arg2, User arg3) throws ServerAuthException
    {
        return false;
    }


    @Override
    public Authentication validateRequest(ServletRequest req, ServletResponse resp, boolean mandatory) throws ServerAuthException
    {
        try
        {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            String redirectUrl = config.redirectURL != null ? config.redirectURL : request.getRequestURL().toString();
            HttpSession session = request.getSession(true);
            
            // check for cached auth
            Authentication cachedAuth = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
            if (cachedAuth != null && cachedAuth instanceof Authentication.User)
            {
                if (!_loginService.validate(((Authentication.User)cachedAuth).getUserIdentity()))
                    session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
                else
                    return cachedAuth;
            }
            
            // if calling back from provider with auth code
            if (generatedState != null && request.getParameter(OAuth.OAUTH_CODE) != null)
            {
                try
                {
                    // first request temporary access token
                    OAuthAuthzResponse oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
                    String code = oar.getCode();
                    log.debug("OAuth Code = " + code);
                    
                    // check state parameter
                    if (!generatedState.equals(oar.getState()))
                        throw OAuthProblemException.error("Invalid state parameter");

                    OAuthClientRequest authRequest = OAuthClientRequest.tokenLocation(config.tokenEndpoint).setCode(code)
                            .setRedirectURI(redirectUrl).setGrantType(GrantType.AUTHORIZATION_CODE).buildBodyMessage();

                    String authString = config.clientID +":"+config.clientSecret;
                    byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
                    String authStringEnc = new String(authEncBytes);

                    authRequest.addHeader("Authorization", "Basic "+authStringEnc);
                    authRequest.addHeader("Accept", "application/json");

                    OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
                    OAuthJSONAccessTokenResponse oAuthResponse = oAuthClient.accessToken(authRequest, HttpMethod.POST);

                    // read access token and store in session
                    String accessToken = oAuthResponse.getAccessToken();
                    log.debug("OAuth Token = " + accessToken);

                    // request user info
                    OAuthClientRequest bearerClientRequest = new OAuthBearerClientRequest(config.userInfoEndpoint).setAccessToken(accessToken).buildQueryMessage();
                    bearerClientRequest.addHeader("Authorization", accessToken);
                    OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, HttpMethod.GET, OAuthResourceResponse.class);

                    // parse user info
                    log.debug("UserInfo = " + resourceResponse.getBody());
                    JsonReader jsonReader = new JsonReader(new StringReader(resourceResponse.getBody()));
                    String userId = parseUserInfoJson(jsonReader);
                    
                    // login and return UserAuth object
                    UserIdentity user = login(userId, "NONE", req);
                    if (user != null)
                    {
                        UserAuthentication userAuth = new UserAuthentication(getAuthMethod(), user);
                        session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, userAuth);
                        return userAuth;
                    }
                    
                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return Authentication.SEND_FAILURE;
                }
                catch (OAuthProblemException | OAuthSystemException | IOException e)
                {
                    log.error("Cannot complete authentication at endpoint " + config.tokenEndpoint, e);
                    response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
                    return Authentication.SEND_FAILURE;
                }
            }

            // first login at auth provider
            else
            {
                try
                {
                    // generate request to auth provider
                    this.generatedState = UUID.randomUUID().toString();
                    OAuthClientRequest authRequest = OAuthClientRequest.authorizationLocation(config.authzEndpoint).setClientId(config.clientID).setRedirectURI(redirectUrl)
                            .setResponseType(OAuth.OAUTH_CODE).setScope(config.authzScope).setState(generatedState).buildQueryMessage();

                    // send as redirect
                    String loginUrl = authRequest.getLocationUri();
                    response.sendRedirect(response.encodeRedirectURL(loginUrl));
                    return Authentication.SEND_CONTINUE;
                }
                catch (OAuthSystemException e)
                {
                    log.error("Cannot redirect to authentication endpoint " + config.authzEndpoint, e);
                    response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
                    return Authentication.SEND_FAILURE;
                }
            }
        }
        catch (IOException e)
        {
            log.error("Cannot send HTTP error", e);
            return Authentication.SEND_FAILURE;
        }
    }


    private String parseUserInfoJson(JsonReader reader) throws IOException
    {
        String userId = null;

        reader.beginObject();        
        while (reader.hasNext())
        {
            String name = reader.nextName();
            if ("id".equals(name) || "user_id".equals(name) || "uid".equals(name))
                userId = reader.nextString();
            else
                reader.skipValue();
        }        
        reader.endObject();
        
        return userId;
    }
}

@@ -969,56 +969,8 @@
 ion;
-%0A%0Aimport org.apache.commons.codec.binary.Base64;
 %0Aimp
@@ -4775,16 +4775,82 @@
 de(code)
+.setClientId(config.clientID).setClientSecret(config.clientSecret)
 %0A       
@@ -4965,331 +4965,8 @@
 e();
-%0A%0A                    String authString = config.clientID +%22:%22+config.clientSecret;%0A                    byte%5B%5D authEncBytes = Base64.encodeBase64(authString.getBytes());%0A                    String authStringEnc = new String(authEncBytes);%0A%0A                    authRequest.addHeader(%22Authorization%22, %22Basic %22+authStringEnc);
 %0A   
@@ -5655,89 +5655,8 @@
 ();%0A
-                    bearerClientRequest.addHeader(%22Authorization%22, accessToken);%0A
     

revert module to previous oauth