/*
 * Copyright 2015 JBoss, by Red Hat, Inc
 *
 * 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 org.uberfire.ext.security.server;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import javax.enterprise.context.ApplicationScoped;
import javax.security.auth.Subject;
import javax.security.jacc.PolicyContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.jboss.errai.bus.server.annotations.Service;
import org.jboss.errai.security.shared.api.Role;
import org.jboss.errai.security.shared.api.identity.User;
import org.jboss.errai.security.shared.api.identity.UserImpl;
import org.jboss.errai.security.shared.exception.FailedAuthenticationException;
import org.jboss.errai.security.shared.service.AuthenticationService;
import org.uberfire.backend.server.security.RoleRegistry;
import org.uberfire.backend.server.security.adapter.GroupAdapterAuthorizationSource;

@Service
@ApplicationScoped
public class ServletSecurityAuthenticationService extends GroupAdapterAuthorizationSource implements AuthenticationService {

    static final String USER_SESSION_ATTR_NAME = "uf.security.user";
    static final String DEFAULT_ROLE_PRINCIPLE_NAME = "Roles";

    private String[] rolePrincipleNames = new String[]{DEFAULT_ROLE_PRINCIPLE_NAME};

    public ServletSecurityAuthenticationService() {
        final String value = System.getProperty("org.uberfire.security.principal.names",
                                                "");
        if (value != null && !value.trim().isEmpty()) {
            rolePrincipleNames = value.split(",");
        }
    }

    protected static HttpServletRequest getRequestForThread() {
        HttpServletRequest request = SecurityIntegrationFilter.getRequest();
        if (request == null) {
            throw new IllegalStateException("This service only works from threads that are handling HTTP servlet requests");
        }
        return request;
    }

    @Override
    public User login(String username,
                      String password) {
        final HttpServletRequest request = getRequestForThread();

        try {
            request.login(username,
                          password);
            return getUser();
        } catch (final ServletException e) {
            throw new FailedAuthenticationException("Failed to authenticate user " + username,
                                                    e);
        }
    }

    @Override
    public boolean isLoggedIn() {
        HttpServletRequest request = getRequestForThread();
        return request.getUserPrincipal() != null;
    }

    @Override
    public void logout() {
        HttpServletRequest request = getRequestForThread();
        try {
            request.logout();
        } catch (Exception e) {

        }
        HttpSession session = request.getSession(false);
        if (session != null) {
            // The try/catch is an ugly hack for EAP 7.0.x with Keycloak (or RH-SSO) adapter.
            // Undertow (1.4.6-) will return what it appears to be a valid session (session != null), but in fact it
            // was already invalidated by the Keycloak adapter during the request.logout() call.
            // See https://issues.jboss.org/browse/RHBPMS-4574
            try {
                session.invalidate();
            } catch (IllegalStateException ise) {
                // Make sure we catch only the intended exception thrown by Undertow. Re-throw any other exception
                String exceptionMessage = ise.getMessage();
                if (exceptionMessage == null || !exceptionMessage.contains("UT000021")) {
                    throw ise;
                }
            }
        }

    }

    @Override
    public User getUser() {
        HttpServletRequest request = getRequestForThread();

        if (request.getUserPrincipal() == null) {
            return null;
        }
        User user = null;
        final HttpSession session = request.getSession();
        if (session != null) {
            user = (User) session.getAttribute(USER_SESSION_ATTR_NAME);
            if (user == null) {
                // Use roles present in the registry.
                final Collection<Role> userRoles = new HashSet<Role>();
                for (final Role checkRole : RoleRegistry.get().getRegisteredRoles()) {
                    if (request.isUserInRole(checkRole.getName())) {
                        userRoles.add(checkRole);
                    }
                }
                // Obtain roles and groups from entities present in the javax security Principal instance.
                final String name = request.getUserPrincipal().getName();
                Subject subject = getSubjectFromPolicyContext();
                List<String> principals = loadEntitiesFromSubjectAndAdapters(name,
                                                                             subject,
                                                                             rolePrincipleNames);
                Collection<Role> roles = getRoles(principals);
                if (null != roles && !roles.isEmpty()) {
                    userRoles.addAll(roles);
                }
                Collection<org.jboss.errai.security.shared.api.Group> userGroups = getGroups(principals);
                // Create the user instance.
                user = new UserImpl(name,
                                    userRoles,
                                    userGroups);
                session.setAttribute(USER_SESSION_ATTR_NAME,
                                     user);
            }
        }

        return user;
    }

    Subject getSubjectFromPolicyContext() {
        Subject subject;
        try {
            subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
        } catch (final Exception e) {
            subject = null;
        }
        return subject;
    }
}