package org.xillium.gear.auth;

import java.util.*;
import java.util.logging.*;
import java.security.cert.X509Certificate;
import javax.servlet.http.*;
import org.xillium.data.*;
import org.xillium.core.*;
import org.springframework.transaction.annotation.Transactional;


/**
 * An Authenticator that authenticates clients by checking their X509 certificates against claimed identities.
 */
public class X509CertificateAuthenticator extends PageAwareAuthenticator {
	private static final Logger _logger = Logger.getLogger(X509CertificateAuthenticator.class.getName());

	private final Persistence _persistence;
    private final String _identityName;
    private final String _qRolesBySubjectName;
    //private String _page;

    /**
     * Constructs an X509CertificateAuthenticator.
     *
     * @param persist - a Persistence object
     * @param identityName - the name of the identity parameter in data binder
     * @param qRolesBySubjectName - a ParametricStatement that retrieves roles by a credential consisting of identity name and subject name,
     *        e.g.
     *  <xmp>
     *  SELECT
     *      MARKET_ID ID, 'market' ROLE_ID, 1 PERMISSION, 1 PREREQUISITE
     *  FROM
     *      MARKETS
     *  WHERE
     *      MARKET_ID = :id:VARCHAR
     *      AND
     *      SUBJECT_NAME = :password:VARCHAR
     *  </xmp>
     */
	public X509CertificateAuthenticator(Persistence persist, String identityName, String qRolesBySubjectName) {
		_persistence = persist;
        _identityName = identityName;
        _qRolesBySubjectName = qRolesBySubjectName;
	}

/*
    public void setAuthenticationPage(String page) {
        _page = page;
    }

    private void redirectToAuthenticationPage(DataBinder binder) {
        if (_page != null) {
            binder.useHashMap(Service.SERVICE_HTTP_HEADER, String.class, String.class).put("Content-Type", "text/html; charset=utf-8");
            binder.put(Service.SERVICE_PAGE_TARGET, _page + "?_redirect_=" + binder.get(Service.REQUEST_TARGET_PATH));
        }
    }
*/

    @Transactional(readOnly=true)
    public List<Role> authenticate(DataBinder binder) throws AuthorizationException {
        try {
            HttpServletRequest req = (HttpServletRequest)binder.getNamedObject(Service.REQUEST_SERVLET_REQ);
            X509Certificate[] certs = (X509Certificate[])req.getAttribute("javax.servlet.request.X509Certificate");

            if (certs != null && certs.length != 0) {
                Credential credential = new Credential(binder.get(_identityName), certs[0].getSubjectX500Principal().getName());
                List<Role> roles = _persistence.getResults(_qRolesBySubjectName, credential, Role.class);
                for (int i = 1; i < certs.length; ++i) {
                    credential.password = certs[i].getSubjectX500Principal().getName();
                    roles.addAll(_persistence.getResults(_qRolesBySubjectName, credential, Role.class));
                }
                if (roles.size() > 0) {
                    return roles;
                } else {
                    throw new AuthorizationException("InvalidSubjectName");
                }
            } else {
                throw new AuthenticationRequiredException("AuthenticationRequired");
            }
        } catch (AuthorizationException x) {
            redirectToAuthenticationPage(binder);
            throw x;
        } catch (Exception x) {
            redirectToAuthenticationPage(binder);
            throw new AuthorizationException(x.getMessage(), x);
        }
    }
}

@@ -116,16 +116,123 @@
 ficate;%0A
+import javax.naming.InvalidNameException;%0Aimport javax.naming.ldap.LdapName;%0Aimport javax.naming.ldap.Rdn;%0A
 import j
@@ -807,18 +807,16 @@
     
-//
 private 
 Stri
@@ -815,19 +815,34 @@
 ate 
-String _pag
+boolean _useFullSubjectNam
 e;%0A%0A
@@ -1019,16 +1019,20 @@
 eter in 
+the 
 data bin
@@ -1144,16 +1144,19 @@
 ting of 
+an 
 identity
@@ -1169,21 +1169,19 @@
 and 
-subject name,
+a password.
 %0A   
@@ -1698,463 +1698,306 @@
 %09%7D%0A%0A
-/
+    /*
 *%0A    
+ 
-public void setAuthenticationPage(String page) %7B%0A        _page = page;%0A    %7D%0A%0A    private void redirectToAuthenticationPage(DataBinder binder) %7B%0A        if (_page != null) %7B
+* Sets whether to check the full subject name. By default, this authenticator only checks the common name part of the subject name inside%0A     * the X509 certificate.
 %0A     
- 
+*/%0A
     
-  binder.useHashMap(Service.SERVICE_HTTP_HEADER, String.class, String.class).put(%22Content-Type%22, %22text/html; charset=utf-8%22);%0A            binder.put(Service.SERVICE_PAGE_TARGET, _page + %22?_redirect_=%22 + binder.get(Service.REQUEST_TARGET_PATH))
+public void setUseFullSubjectName(boolean full) %7B%0A        _useFullSubjectName = full
 ;%0A    
- 
-   
 %7D
+%0A
 %0A    
-%7D%0A*/%0A
+@Override
 %0A   
@@ -2489,16 +2489,37 @@
 yName), 
+getPrincipalIdentity(
 certs%5B0%5D
@@ -2555,16 +2555,98 @@
 tName())
+);%0A                _logger.fine(org.xillium.base.beans.Beans.toString(credential))
 ;%0A      
@@ -2844,16 +2844,37 @@
 sword = 
+getPrincipalIdentity(
 certs%5Bi%5D
@@ -2909,16 +2909,102 @@
 etName()
+);%0A                    _logger.fine(org.xillium.base.beans.Beans.toString(credential))
 ;%0A      
@@ -3711,11 +3711,430 @@
  %7D%0A    %7D
+%0A%0A    private final String getPrincipalIdentity(String name) throws InvalidNameException %7B%0A        if (_useFullSubjectName) %7B%0A            return name;%0A        %7D else %7B%0A            for(Rdn rdn: new LdapName(name).getRdns()) %7B%0A                if (%22CN%22.equalsIgnoreCase(rdn.getType())) return rdn.getValue().toString();%0A            %7D%0A            throw new InvalidNameException(%22NoCommonNameInSubjectName%22);%0A        %7D%0A    %7D
 %0A%7D%0A

use only the CN part by default (charset complexity in other parts)