/*
 * 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.felix.webconsole.internal.servlet;


import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.apache.felix.webconsole.WebConsoleSecurityProvider;
import org.apache.felix.webconsole.WebConsoleSecurityProvider2;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.util.tracker.ServiceTracker;


final class OsgiManagerHttpContext implements HttpContext
{

    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";

    private static final String HEADER_AUTHORIZATION = "Authorization";

    private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";

    private final HttpContext base;

    private final ServiceTracker tracker;

    private final String username;

    private final String password;

    private final String realm;


    OsgiManagerHttpContext( HttpService httpService, final ServiceTracker tracker, final String username,
        final String password, final String realm )
    {
        this.tracker = tracker;
        this.username = username;
        this.password = password;
        this.realm = realm;
        this.base = httpService.createDefaultHttpContext();
    }


    public String getMimeType( String name )
    {
        return this.base.getMimeType( name );
    }


    public URL getResource( String name )
    {
        URL url = this.base.getResource( name );
        if ( url == null && name.endsWith( "/" ) )
        {
            return this.base.getResource( name.substring( 0, name.length() - 1 ) );
        }
        return url;
    }


    /**
     * Checks the <code>Authorization</code> header of the request for Basic
     * authentication user name and password. If contained, the credentials are
     * compared to the user name and password set for the OSGi Console.
     * <p>
     * If no user name is set, the <code>Authorization</code> header is
     * ignored and the client is assumed to be authenticated.
     *
     * @param originalRequest The HTTP request used to get the
     *            <code>Authorization</code> header.
     * @param response The HTTP response used to send the authentication request
     *            if authentication is required but not satisfied.
     * @return <code>true</code> if authentication is required and not
     *         satisfied by the request.
     */
    public boolean handleSecurity( final HttpServletRequest originalRequest, HttpServletResponse response )
    {
        Object provider = tracker.getService();

        HttpServletRequest request = originalRequest;
        if (OsgiManager.isLoginRequired(originalRequest)) {
            request = forceLogin(originalRequest);
        }

        // check whether the security provider can fully handle the request
        if ( provider instanceof WebConsoleSecurityProvider2 )
        {
            return ( ( WebConsoleSecurityProvider2 ) provider ).authenticate( request, response );
        }

        // Return immediately if the header is missing
        String authHeader = request.getHeader( HEADER_AUTHORIZATION );
        if ( authHeader != null && authHeader.length() > 0 )
        {

            // Get the authType (Basic, Digest) and authInfo (user/password)
            // from
            // the header
            authHeader = authHeader.trim();
            int blank = authHeader.indexOf( ' ' );
            if ( blank > 0 )
            {
                String authType = authHeader.substring( 0, blank );
                String authInfo = authHeader.substring( blank ).trim();

                // Check whether authorization type matches
                if ( authType.equalsIgnoreCase( AUTHENTICATION_SCHEME_BASIC ) )
                {
                    try
                    {
                        String srcString = base64Decode( authInfo );
                        int i = srcString.indexOf( ':' );
                        String username = srcString.substring( 0, i );
                        String password = srcString.substring( i + 1 );

                        // authenticate
                        if ( authenticate( provider, username, password ) )
                        {
                            // as per the spec, set attributes
                            request.setAttribute( HttpContext.AUTHENTICATION_TYPE, HttpServletRequest.BASIC_AUTH );
                            request.setAttribute( HttpContext.REMOTE_USER, username );

                            // set web console user attribute
                            request.setAttribute( WebConsoleSecurityProvider2.USER_ATTRIBUTE, username );

                            // succeed
                            return true;
                        }
                    }
                    catch ( Exception e )
                    {
                        // Ignore
                    }
                }
            }
        }

        // request authentication
        try
        {
            response.setHeader( HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC + " realm=\"" + this.realm + "\"" );
            response.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
            response.setContentLength( 0 );
            response.flushBuffer();
        }
        catch ( IOException ioe )
        {
            // failed sending the response ... cannot do anything about it
        }

        // inform HttpService that authentication failed
        return false;
    }

    public boolean authorize( final HttpServletRequest request, String role )
    {
        Object user = request.getAttribute( WebConsoleSecurityProvider2.USER_ATTRIBUTE );
        if ( user != null )
        {
            WebConsoleSecurityProvider provider = ( WebConsoleSecurityProvider ) tracker.getService();
            if ( provider != null )
            {
                return provider.authorize( user, role );
            }

            // no provider, grant access (backwards compatibility)
            return true;
        }

        // missing user in the request, deny access
        return false;
    }


    private static String base64Decode( String srcString )
    {
        byte[] transformed = Base64.decodeBase64( srcString );
        try
        {
            return new String( transformed, "ISO-8859-1" );
        }
        catch ( UnsupportedEncodingException uee )
        {
            return new String( transformed );
        }
    }


    private boolean authenticate( Object provider, String username, String password )
    {
        if ( provider != null )
        {
            return ( ( WebConsoleSecurityProvider ) provider ).authenticate( username, password ) != null;
        }
        if ( this.username.equals( username ) && this.password.equals( password ) )
        {
            return true;
        }
        return false;
    }

    /*
     * Force a new login by wrapping the request to hide the previous authorization header
     */
    private HttpServletRequest forceLogin(final HttpServletRequest request)
    {
        return new HttpServletRequestWrapper(request) {

            public String getHeader(String name) {
                if ( HEADER_AUTHORIZATION.equals(name) ) {
                    return null;
                } else {
                    return super.getHeader(name);
                }
            }

        };
    }

}
