SsoAuthenticationHandler.java 14.5 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  javax.servlet.http.Cookie
 *  javax.servlet.http.HttpServletRequest
 *  javax.servlet.http.HttpServletResponse
 *  org.apache.commons.codec.binary.Base64
 *  org.apache.felix.scr.annotations.Activate
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.ConfigurationPolicy
 *  org.apache.felix.scr.annotations.Deactivate
 *  org.apache.felix.scr.annotations.Properties
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.auth.core.spi.AuthenticationHandler
 *  org.apache.sling.auth.core.spi.AuthenticationInfo
 *  org.apache.sling.commons.osgi.OsgiUtil
 *  org.osgi.framework.BundleContext
 *  org.osgi.service.component.ComponentContext
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.adobe.granite.auth.sso.impl;

import com.adobe.granite.auth.sso.impl.JaasHelper;
import com.adobe.granite.auth.sso.impl.SSOCredentials;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.auth.core.spi.AuthenticationHandler;
import org.apache.sling.auth.core.spi.AuthenticationInfo;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(label="%auth.sso.name", description="%auth.sso.description", metatype=1, policy=ConfigurationPolicy.REQUIRE)
@Service(value={AuthenticationHandler.class})
@Properties(value={@Property(name="service.description", value={"Adobe Granite SSO Authentication Handler"}), @Property(name="path", value={"/"}), @Property(name="service.ranking", intValue={0}, propertyPrivate=0), @Property(name="jaas.controlFlag", value={"sufficient"}), @Property(name="jaas.realmName", value={"jackrabbit.oak"}), @Property(name="jaas.ranking", intValue={1000})})
public class SsoAuthenticationHandler
implements AuthenticationHandler {
    private final Logger log;
    @Property(name="authtype", propertyPrivate=1)
    private static final String TYPE = "SSO";
    @Property(cardinality=Integer.MAX_VALUE)
    public static final String PROPERTY_HEADERS = "headers";
    public static final String DEFAULT_COOKIE_NAME = "cqpsso";
    @Property(cardinality=Integer.MAX_VALUE, value={"cqpsso"})
    public static final String PROPERTY_COOKIES = "cookies";
    @Property(cardinality=Integer.MAX_VALUE)
    public static final String PROPERTY_PARAMETERS = "parameters";
    @Property(cardinality=Integer.MAX_VALUE)
    public static final String PROPERTY_USERMAP = "usermap";
    private static final String DEFAULT_FORMAT = "Basic";
    @Property(value={"Basic"})
    public static final String PROPERTY_FORMAT = "format";
    private static final String DEFAULT_TRUSTED_CREDENTIALS_ATTRIBUTE = "";
    @Property(value={""})
    public static final String PROPERTY_TRUSTED_CREDENTIALS_ATTRIBUTE = "trustedCredentialsAttribute";
    private String[] headerNames;
    private String[] cookieNames;
    private String[] parameterNames;
    private Map<String, String> userMap;
    private Decoder decoder;
    private String trustedCredentialsAttribute;
    private final JaasHelper jaasHelper;

    public SsoAuthenticationHandler() {
        this.log = LoggerFactory.getLogger((String)this.getClass().getName());
        this.jaasHelper = new JaasHelper();
    }

    @Activate
    private void activate(ComponentContext ctx) {
        Dictionary properties = ctx.getProperties();
        this.jaasHelper.open(ctx.getBundleContext(), properties);
        this.headerNames = SsoAuthenticationHandler.toCleanStringArray(properties.get("headers"));
        this.cookieNames = SsoAuthenticationHandler.toCleanStringArray(properties.get("cookies"));
        this.parameterNames = SsoAuthenticationHandler.toCleanStringArray(properties.get("parameters"));
        this.userMap = SsoAuthenticationHandler.parseUserMap(SsoAuthenticationHandler.toCleanStringArray(properties.get("usermap")));
        this.trustedCredentialsAttribute = OsgiUtil.toString(properties.get("trustedCredentialsAttribute"), (String)"");
        String formatSpec = OsgiUtil.toString(properties.get("format"), (String)"Basic").trim();
        if (formatSpec.length() == 0) {
            formatSpec = "Basic";
        }
        this.decoder = formatSpec.equals("AsIs") ? new Decoder() : (formatSpec.equals("Basic") ? new HttpBasicDecoder() : new RegexDecoder(formatSpec));
        this.log.info("SSO Authentication Handler configured: {}", (Object)this);
    }

    @Deactivate
    private void deactivate() {
        this.jaasHelper.close();
    }

    public AuthenticationInfo extractCredentials(HttpServletRequest request, HttpServletResponse response) {
        SSOInfo ssoInfo = this.getSsoUid(request);
        if (ssoInfo != null) {
            String uid = this.decoder.decode(ssoInfo.ssoUid);
            if (uid != null && uid.length() != 0) {
                String mappedUser = this.userMap.get(uid);
                String userId = mappedUser == null ? uid : mappedUser;
                AuthenticationInfo info = new AuthenticationInfo("SSO", userId);
                if (this.jaasHelper.enabled()) {
                    info.put("user.jcr.credentials", (Object)new SSOCredentials(userId));
                } else {
                    info.put(this.trustedCredentialsAttribute, (Object)userId);
                }
                return info;
            }
            this.log.info("Unable to decode user ID {}", (Object)uid);
            return null;
        }
        return null;
    }

    public boolean requestCredentials(HttpServletRequest req, HttpServletResponse res) {
        return false;
    }

    public void dropCredentials(HttpServletRequest req, HttpServletResponse res) {
    }

    public String toString() {
        StringBuilder sb = new StringBuilder("SsoAuthenticationHandler{");
        sb.append("jaasEnabled=").append(this.jaasHelper.enabled());
        sb.append(", headerNames=").append(Arrays.toString(this.headerNames));
        sb.append(", cookieNames=").append(Arrays.toString(this.cookieNames));
        sb.append(", parameterNames=").append(Arrays.toString(this.parameterNames));
        sb.append(", trustedCredentialsAttribute='").append(this.trustedCredentialsAttribute).append('\'');
        sb.append('}');
        return sb.toString();
    }

    private static String[] toCleanStringArray(Object rawProperty) {
        String[] converted = OsgiUtil.toStringArray((Object)rawProperty);
        if (converted == null || converted.length == 0) {
            return null;
        }
        ArrayList<String> checked = new ArrayList<String>(converted.length);
        for (String entry : converted) {
            if (entry == null || entry.length() <= 0) continue;
            checked.add(entry);
        }
        if (checked.size() == 0) {
            return null;
        }
        return checked.toArray(new String[checked.size()]);
    }

    private static Map<String, String> parseUserMap(String[] pairs) {
        HashMap<String, String> result = new HashMap<String, String>();
        if (pairs != null) {
            for (String pair : pairs) {
                String value;
                String[] split = pair.replace("\\=", "=").split("=", 2);
                String key = split[0];
                String string = value = split.length > 1 ? split[1] : "";
                if ("admin".equalsIgnoreCase(key) || "admin".equalsIgnoreCase(value)) continue;
                result.put(key, value);
            }
        }
        return result;
    }

    private SSOInfo getSsoUid(HttpServletRequest request) {
        SSOInfo ssoInfo = this.getSsoUidFromHeader(request);
        if (ssoInfo == null && (ssoInfo = this.getSsoUidFromCookie(request)) == null) {
            ssoInfo = this.getSsoUidFromParameter(request);
        }
        return ssoInfo;
    }

    private SSOInfo getSsoUidFromHeader(HttpServletRequest request) {
        String[] headerNames = this.headerNames;
        if (headerNames != null) {
            for (String headerName : headerNames) {
                String value = request.getHeader(headerName);
                if (value == null) continue;
                this.log.debug("found header {}={}", (Object)headerName, (Object)value);
                return new SSOInfo(value, "header", headerName);
            }
        }
        return null;
    }

    private SSOInfo getSsoUidFromParameter(HttpServletRequest request) {
        String[] parameterNames = this.parameterNames;
        if (parameterNames != null) {
            for (String parameterName : parameterNames) {
                String value = request.getParameter(parameterName);
                if (value == null) continue;
                this.log.debug("found parameter {}={}", (Object)parameterName, (Object)value);
                return new SSOInfo(value, "parameter", parameterName);
            }
        }
        return null;
    }

    private SSOInfo getSsoUidFromCookie(HttpServletRequest request) {
        Cookie[] cookies;
        String[] cookieNames = this.cookieNames;
        if (cookieNames != null && (cookies = request.getCookies()) != null) {
            for (String cookieName : cookieNames) {
                for (Cookie cookie : cookies) {
                    if (!cookieName.equalsIgnoreCase(cookie.getName())) continue;
                    String value = cookie.getValue();
                    this.log.debug("found cookie {}={}", (Object)cookieName, (Object)value);
                    try {
                        value = URLDecoder.decode(value, "UTF-8");
                    }
                    catch (UnsupportedEncodingException e) {
                        // empty catch block
                    }
                    return new SSOInfo(value, "cookie", cookieName);
                }
            }
        }
        return null;
    }

    private static class RegexDecoder
    extends Decoder {
        static final String FORMAT = "Regex";
        private Pattern pattern;
        private int matchGroup;

        RegexDecoder(String regexSpec) {
            super();
            try {
                int matchGroup;
                String patternString;
                int pos = regexSpec.lastIndexOf(124);
                String string = patternString = pos == -1 ? regexSpec : regexSpec.substring(0, pos);
                if (pos != -1) {
                    String groupString = regexSpec.substring(pos + 1);
                    try {
                        matchGroup = Integer.parseInt(groupString);
                    }
                    catch (NumberFormatException nfe) {
                        this.log.error("Cannot parse match group '{}' to a number; assuming default", (Object)groupString);
                        matchGroup = -1;
                    }
                } else {
                    matchGroup = -1;
                }
                this.pattern = Pattern.compile(patternString);
                this.matchGroup = matchGroup;
            }
            catch (PatternSyntaxException pse) {
                this.log.error("Unable to parse regexp: - defaulting to 'as is' format!" + regexSpec, (Throwable)pse);
            }
        }

        @Override
        String decode(String value) {
            Matcher matcher = this.pattern.matcher(value);
            if (matcher.find()) {
                int group = this.matchGroup >= 0 ? Math.min(this.matchGroup, matcher.groupCount()) : (matcher.groupCount() > 0 ? 1 : 0);
                return matcher.group(group);
            }
            this.log.info("Value {} does not match expression {}.", (Object)value, (Object)this.pattern.pattern());
            return null;
        }

        @Override
        public String toString() {
            return "Regex:" + this.pattern;
        }
    }

    private static class HttpBasicDecoder
    extends Decoder {
        static final String FORMAT = "Basic";

        private HttpBasicDecoder() {
            super();
        }

        @Override
        String decode(String authHeader) {
            String decoded;
            String[] parts = authHeader.split(" ");
            if (parts.length < 2) {
                this.log.info("decodeAuthorizationHeader: Not a valid Authorization header {}", (Object)authHeader);
                return null;
            }
            String authType = parts[0];
            String authInfo = parts[1];
            if (!authType.equalsIgnoreCase("Basic")) {
                this.log.info("decodeAuthorizationHeader: Unsupported HTTP authentication scheme {}", (Object)authType);
                return null;
            }
            try {
                byte[] encoded = authInfo.getBytes("ISO-8859-1");
                byte[] bytes = Base64.decodeBase64((byte[])encoded);
                decoded = new String(bytes, "ISO-8859-1");
            }
            catch (UnsupportedEncodingException uee) {
                this.log.error("decodeAuthorizationHeader: Cannot en/decode authentication info", (Throwable)uee);
                return null;
            }
            int colon = decoded.indexOf(58);
            return colon < 0 ? decoded : decoded.substring(0, colon);
        }

        @Override
        public String toString() {
            return "Basic";
        }
    }

    private static class Decoder {
        static final String FORMAT = "AsIs";
        protected final Logger log;

        private Decoder() {
            this.log = LoggerFactory.getLogger(this.getClass());
        }

        String decode(String value) {
            return value;
        }

        public String toString() {
            return "AsIs";
        }
    }

    private static final class SSOInfo {
        public String ssoUid;
        public String providerId;

        public SSOInfo(String id, String provider, String name) {
            this.ssoUid = id;
            this.providerId = provider + ":" + name;
        }
    }

}