org.xwiki.contrib.oidc.auth.internal.OIDCClientConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.contrib.oidc.auth.internal.OIDCClientConfiguration.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.contrib.oidc.auth.internal;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.LocalDateTime;
import org.xwiki.component.annotation.Component;
import org.xwiki.configuration.ConfigurationSource;
import org.xwiki.container.Container;
import org.xwiki.container.Request;
import org.xwiki.container.Session;
import org.xwiki.container.servlet.ServletSession;
import org.xwiki.contrib.oidc.OIDCIdToken;
import org.xwiki.contrib.oidc.OIDCUserInfo;
import org.xwiki.contrib.oidc.internal.OIDCConfiguration;
import org.xwiki.contrib.oidc.provider.internal.OIDCManager;
import org.xwiki.contrib.oidc.provider.internal.endpoint.AuthorizationOIDCEndpoint;
import org.xwiki.contrib.oidc.provider.internal.endpoint.TokenOIDCEndpoint;
import org.xwiki.contrib.oidc.provider.internal.endpoint.UserInfoOIDCEndpoint;
import org.xwiki.instance.InstanceIdManager;
import org.xwiki.properties.ConverterManager;

import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.auth.Secret;
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.State;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.openid.connect.sdk.ClaimsRequest;
import com.nimbusds.openid.connect.sdk.OIDCScopeValue;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;

/**
 * Various OpenID Connect authenticator configurations.
 * 
 * @version $Id$
 */
@Component(roles = OIDCClientConfiguration.class)
@Singleton
public class OIDCClientConfiguration extends OIDCConfiguration {
    public class GroupMapping {
        private final Map<String, Set<String>> xwikiMapping;

        private final Map<String, Set<String>> providerMapping;

        /**
         * @param size
         */
        public GroupMapping(int size) {
            this.xwikiMapping = new HashMap<>(size);
            this.providerMapping = new HashMap<>(size);
        }

        public Set<String> fromXWiki(String xwikiGroup) {
            return this.xwikiMapping.get(xwikiGroup);
        }

        public Set<String> fromProvider(String providerGroup) {
            return this.providerMapping.get(providerGroup);
        }
    }

    public static final String PROP_XWIKIPROVIDER = "oidc.xwikiprovider";

    public static final String PROP_USER_NAMEFORMATER = "oidc.user.nameFormater";

    public static final String DEFAULT_USER_NAMEFORMATER = "${oidc.issuer.host.clean}-${oidc.user.preferredUsername.clean}";

    /**
     * @since 1.11
     */
    public static final String PROP_USER_SUBJECTFORMATER = "oidc.user.subjectFormater";

    /**
     * @since 1.11
     */
    public static final String DEFAULT_USER_SUBJECTFORMATER = "${oidc.user.subject}";

    public static final String PROPPREFIX_ENDPOINT = "oidc.endpoint.";

    public static final String PROP_ENDPOINT_AUTHORIZATION = PROPPREFIX_ENDPOINT + AuthorizationOIDCEndpoint.HINT;

    public static final String PROP_ENDPOINT_TOKEN = PROPPREFIX_ENDPOINT + TokenOIDCEndpoint.HINT;

    public static final String PROP_ENDPOINT_USERINFO = PROPPREFIX_ENDPOINT + UserInfoOIDCEndpoint.HINT;

    public static final String PROP_CLIENTID = "oidc.clientid";

    /**
     * @since 1.13
     */
    public static final String PROP_SECRET = "oidc.secret";

    public static final String PROP_SKIPPED = "oidc.skipped";

    /**
     * @since 1.13
     */
    public static final String PROP_ENDPOINT_TOKEN_AUTH_METHOD = "oidc.endpoint.token.auth_method";

    /**
     * @since 1.13
     */
    public static final String PROP_ENDPOINT_USERINFO_METHOD = "oidc.endpoint.userinfo.method";

    /**
     * @since 1.12
     */
    public static final String PROP_USERINFOREFRESHRATE = "oidc.userinforefreshrate";

    public static final String PROP_USERINFOCLAIMS = "oidc.userinfoclaims";

    public static final List<String> DEFAULT_USERINFOCLAIMS = Arrays.asList(OIDCUserInfo.CLAIM_XWIKI_ACCESSIBILITY,
            OIDCUserInfo.CLAIM_XWIKI_COMPANY, OIDCUserInfo.CLAIM_XWIKI_DISPLAYHIDDENDOCUMENTS,
            OIDCUserInfo.CLAIM_XWIKI_EDITOR, OIDCUserInfo.CLAIM_XWIKI_USERTYPE);

    public static final String PROP_IDTOKENCLAIMS = "oidc.idtokenclaims";

    public static final List<String> DEFAULT_IDTOKENCLAIMS = Arrays.asList(OIDCIdToken.CLAIM_XWIKI_INSTANCE_ID);

    /**
     * @since 1.10
     */
    public static final String PROP_GROUPS_MAPPING = "oidc.groups.mapping";

    /**
     * @since 1.10
     */
    public static final String PROP_GROUPS_ALLOWED = "oidc.groups.allowed";

    /**
     * @since 1.10
     */
    public static final String PROP_GROUPS_FORBIDDEN = "oidc.groups.forbidden";

    public static final String PROP_INITIAL_REQUEST = "xwiki.initialRequest";

    public static final String PROP_STATE = "oidc.state";

    public static final String PROP_SESSION_ACCESSTOKEN = "oidc.accesstoken";

    public static final String PROP_SESSION_IDTOKEN = "oidc.idtoken";

    public static final String PROP_SESSION_USERINFO_EXPORATIONDATE = "oidc.session.userinfoexpirationdate";

    private static final String XWIKI_GROUP_PREFIX = "XWiki.";

    @Inject
    private InstanceIdManager instance;

    @Inject
    private OIDCManager manager;

    @Inject
    private Container container;

    @Inject
    private ConverterManager converter;

    @Inject
    // TODO: store configuration in custom objects
    private ConfigurationSource configuration;

    private HttpSession getHttpSession() {
        Session session = this.container.getSession();
        if (session instanceof ServletSession) {
            return ((ServletSession) session).getHttpSession();
        }

        return null;
    }

    private <T> T getSessionAttribute(String name) {
        HttpSession session = getHttpSession();
        if (session != null) {
            return (T) session.getAttribute(name);
        }

        return null;
    }

    private <T> T removeSessionAttribute(String name) {
        HttpSession session = getHttpSession();
        if (session != null) {
            try {
                return (T) session.getAttribute(name);
            } finally {
                session.removeAttribute(name);
            }
        }

        return null;
    }

    private void setSessionAttribute(String name, Object value) {
        HttpSession session = getHttpSession();
        if (session != null) {
            session.setAttribute(name, value);
        }
    }

    private String getRequestParameter(String key) {
        Request request = this.container.getRequest();
        if (request != null) {
            return (String) request.getProperty(key);
        }

        return null;
    }

    @Override
    protected <T> T getProperty(String key, Class<T> valueClass) {
        // Get property from request
        String requestValue = getRequestParameter(key);
        if (requestValue != null) {
            return this.converter.convert(valueClass, requestValue);
        }

        // Get property from session
        T sessionValue = getSessionAttribute(key);
        if (sessionValue != null) {
            return sessionValue;
        }

        // Get property from configuration
        return this.configuration.getProperty(key, valueClass);
    }

    @Override
    protected <T> T getProperty(String key, T def) {
        // Get property from request
        String requestValue = getRequestParameter(key);
        if (requestValue != null) {
            return this.converter.convert(def.getClass(), requestValue);
        }

        // Get property from session
        T sessionValue = getSessionAttribute(key);
        if (sessionValue != null) {
            return sessionValue;
        }

        // Get property from configuration
        return this.configuration.getProperty(key, def);
    }

    /**
     * @since 1.11
     */
    public String getSubjectdFormater() {
        String userFormatter = getProperty(PROP_USER_SUBJECTFORMATER, String.class);
        if (userFormatter == null) {
            userFormatter = DEFAULT_USER_SUBJECTFORMATER;
        }

        return userFormatter;
    }

    /**
     * @since 1.11
     */
    public String getXWikiUserNameFormater() {
        String userFormatter = getProperty(PROP_USER_NAMEFORMATER, String.class);
        if (userFormatter == null) {
            userFormatter = DEFAULT_USER_NAMEFORMATER;
        }

        return userFormatter;
    }

    public URL getXWikiProvider() {
        return getProperty(PROP_XWIKIPROVIDER, URL.class);
    }

    private URI getEndPoint(String hint) throws URISyntaxException, MalformedURLException {
        URL endpoint = getProperty(PROPPREFIX_ENDPOINT + hint, URL.class);

        // If no direct endpoint is provider assume it's a XWiki OIDC provider and generate the endpoint from the hint
        if (endpoint == null) {
            URL provider = getXWikiProvider();
            if (provider != null) {
                endpoint = this.manager.createEndPointURI(getXWikiProvider().toURI().toString(), hint).toURL();
            }
        }

        return endpoint == null ? null : endpoint.toURI();
    }

    public URI getAuthorizationOIDCEndpoint() throws URISyntaxException, MalformedURLException {
        return getEndPoint(AuthorizationOIDCEndpoint.HINT);
    }

    public URI getTokenOIDCEndpoint() throws URISyntaxException, MalformedURLException {
        return getEndPoint(TokenOIDCEndpoint.HINT);
    }

    public URI getUserInfoOIDCEndpoint() throws URISyntaxException, MalformedURLException {
        return getEndPoint(UserInfoOIDCEndpoint.HINT);
    }

    public ClientID getClientID() {
        String clientId = getProperty(PROP_CLIENTID, String.class);

        // Fallback on instance id
        return new ClientID(clientId != null ? clientId : this.instance.getInstanceId().getInstanceId());
    }

    /**
     * @since 1.13
     */
    public Secret getSecret() {
        String secret = getProperty(PROP_SECRET, String.class);
        if (StringUtils.isBlank(secret)) {
            return null;
        } else {
            return new Secret(secret);
        }
    }

    /**
     * @since 1.13
     */
    public ClientAuthenticationMethod getTokenEndPointAuthMethod() {
        String authMethod = getProperty(PROP_ENDPOINT_TOKEN_AUTH_METHOD, String.class);
        if ("client_secret_post".equalsIgnoreCase(authMethod)) {
            return ClientAuthenticationMethod.CLIENT_SECRET_POST;
        } else {
            return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
        }
    }

    /**
     * @since 1.13
     */
    public HTTPRequest.Method getUserInfoEndPointMethod() {
        return getProperty(PROP_ENDPOINT_USERINFO_METHOD, HTTPRequest.Method.GET);
    }

    public String getSessionState() {
        return getSessionAttribute(PROP_STATE);
    }

    public boolean isSkipped() {
        return getProperty(PROP_SKIPPED, false);
    }

    /**
     * @since 1.2
     */
    public ClaimsRequest getClaimsRequest() {
        // TODO: allow passing the complete JSON as configuration
        ClaimsRequest claimsRequest = new ClaimsRequest();

        // ID Token claims
        List<String> idtokenclaims = getIDTokenClaims();
        if (idtokenclaims != null && !idtokenclaims.isEmpty()) {
            // ID Token claims
            for (String claim : idtokenclaims) {
                claimsRequest.addIDTokenClaim(claim);
            }
        }

        // UserInfo claims
        List<String> userinfoclaims = getUserInfoClaims();
        if (userinfoclaims != null && !userinfoclaims.isEmpty()) {
            for (String claim : userinfoclaims) {
                claimsRequest.addUserInfoClaim(claim);
            }
        }

        return claimsRequest;
    }

    /**
     * @since 1.2
     */
    public List<String> getIDTokenClaims() {
        return getProperty(PROP_IDTOKENCLAIMS, DEFAULT_IDTOKENCLAIMS);
    }

    /**
     * @since 1.2
     */
    public List<String> getUserInfoClaims() {
        return getProperty(PROP_USERINFOCLAIMS, DEFAULT_USERINFOCLAIMS);
    }

    /**
     * @since 1.12
     */
    public int getUserInfoRefreshRate() {
        return getProperty(PROP_USERINFOREFRESHRATE, 600000);
    }

    /**
     * @since 1.2
     */
    public Scope getScope() {
        return new Scope(OIDCScopeValue.OPENID, OIDCScopeValue.PROFILE, OIDCScopeValue.EMAIL,
                OIDCScopeValue.ADDRESS, OIDCScopeValue.PHONE);
    }

    /**
     * @since 1.10
     */
    public GroupMapping getGroupMapping() {
        List<String> groupsMapping = getProperty(PROP_GROUPS_MAPPING, List.class);

        GroupMapping groups;

        if (groupsMapping != null && !groupsMapping.isEmpty()) {
            groups = new GroupMapping(groupsMapping.size());

            for (String groupMapping : groupsMapping) {
                int index = groupMapping.indexOf('=');

                if (index != -1) {
                    String xwikiGroup = toXWikiGroup(groupMapping.substring(0, index));
                    String providerGroup = groupMapping.substring(index + 1);

                    // Add to XWiki mapping
                    Set<String> providerGroups = groups.xwikiMapping.computeIfAbsent(xwikiGroup,
                            k -> new HashSet<>());
                    providerGroups.add(providerGroup);

                    // Add to provider mapping
                    Set<String> xwikiGroups = groups.providerMapping.computeIfAbsent(providerGroup,
                            k -> new HashSet<>());
                    xwikiGroups.add(xwikiGroup);
                }
            }
        } else {
            groups = null;
        }

        return groups;
    }

    /**
     * @since 1.10
     */
    public String toXWikiGroup(String group) {
        return group.startsWith(XWIKI_GROUP_PREFIX) ? group : XWIKI_GROUP_PREFIX + group;
    }

    /**
     * @since 1.10
     */
    public List<String> getAllowedGroups() {
        List<String> groups = getProperty(PROP_GROUPS_ALLOWED, List.class);

        return groups != null && !groups.isEmpty() ? groups : null;
    }

    /**
     * @since 1.10
     */
    public List<String> getForbiddenGroups() {
        List<String> groups = getProperty(PROP_GROUPS_FORBIDDEN, List.class);

        return groups != null && !groups.isEmpty() ? groups : null;
    }

    // Session only

    /**
     * @since 1.2
     */
    public Date removeUserInfoExpirationDate() {
        return removeSessionAttribute(PROP_SESSION_USERINFO_EXPORATIONDATE);
    }

    /**
     * @since 1.2
     */
    public void setUserInfoExpirationDate(Date date) {
        setSessionAttribute(PROP_SESSION_USERINFO_EXPORATIONDATE, date);
    }

    /**
     * @since 1.2
     */
    public void resetUserInfoExpirationDate() {
        LocalDateTime expiration = LocalDateTime.now().plusMillis(getUserInfoRefreshRate());

        setUserInfoExpirationDate(expiration.toDate());
    }

    /**
     * @since 1.2
     */
    public BearerAccessToken getAccessToken() {
        return getSessionAttribute(PROP_SESSION_ACCESSTOKEN);
    }

    /**
     * @since 1.2
     */
    public void setAccessToken(BearerAccessToken accessToken) {
        setSessionAttribute(PROP_SESSION_ACCESSTOKEN, accessToken);
    }

    /**
     * @since 1.2
     */
    public IDTokenClaimsSet getIdToken() {
        return getSessionAttribute(PROP_SESSION_IDTOKEN);
    }

    /**
     * @since 1.2
     */
    public void setIdToken(IDTokenClaimsSet idToken) {
        setSessionAttribute(PROP_SESSION_IDTOKEN, idToken);
    }

    /**
     * @since 1.2
     */
    public URI getSuccessRedirectURI() {
        URI uri = getSessionAttribute(PROP_INITIAL_REQUEST);
        if (uri == null) {
            // TODO: return wiki hope page
        }

        return uri;
    }

    /**
     * @since 1.2
     */
    public void setSuccessRedirectURI(URI uri) {
        setSessionAttribute(PROP_INITIAL_REQUEST, uri);
    }

    /**
     * @return true if groups should be synchronized (in which case if the provider does not answer to the group claim
     *         it means the user does not belong to any group)
     * @since 1.14
     */
    public boolean isGroupSync() {
        String groupClaim = getGroupClaim();

        return getUserInfoClaims().contains(groupClaim);
    }
}