nifi-issues mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "ASF GitHub Bot (JIRA)" <j...@apache.org>
Subject [jira] [Commented] (NIFI-4210) Add OpenId Connect support for authenticating users
Date Wed, 02 Aug 2017 18:02:00 GMT

    [ https://issues.apache.org/jira/browse/NIFI-4210?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16111433#comment-16111433
] 

ASF GitHub Bot commented on NIFI-4210:
--------------------------------------

Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2047#discussion_r130951002
  
    --- Diff: nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
---
    @@ -0,0 +1,339 @@
    +/*
    + * 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.nifi.web.security.oidc;
    +
    +import com.nimbusds.jose.JOSEException;
    +import com.nimbusds.jose.JWSAlgorithm;
    +import com.nimbusds.jose.proc.BadJOSEException;
    +import com.nimbusds.jose.util.DefaultResourceRetriever;
    +import com.nimbusds.jose.util.ResourceRetriever;
    +import com.nimbusds.jwt.JWT;
    +import com.nimbusds.jwt.JWTClaimsSet;
    +import com.nimbusds.oauth2.sdk.AuthorizationGrant;
    +import com.nimbusds.oauth2.sdk.ParseException;
    +import com.nimbusds.oauth2.sdk.Scope;
    +import com.nimbusds.oauth2.sdk.TokenErrorResponse;
    +import com.nimbusds.oauth2.sdk.TokenRequest;
    +import com.nimbusds.oauth2.sdk.TokenResponse;
    +import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
    +import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
    +import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
    +import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
    +import com.nimbusds.oauth2.sdk.auth.Secret;
    +import com.nimbusds.oauth2.sdk.http.HTTPRequest;
    +import com.nimbusds.oauth2.sdk.http.HTTPResponse;
    +import com.nimbusds.oauth2.sdk.id.ClientID;
    +import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
    +import com.nimbusds.openid.connect.sdk.OIDCScopeValue;
    +import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
    +import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
    +import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
    +import com.nimbusds.openid.connect.sdk.UserInfoRequest;
    +import com.nimbusds.openid.connect.sdk.UserInfoResponse;
    +import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
    +import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
    +import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
    +import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
    +import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator;
    +import net.minidev.json.JSONObject;
    +import org.apache.commons.lang3.StringUtils;
    +import org.apache.nifi.util.FormatUtils;
    +import org.apache.nifi.util.NiFiProperties;
    +import org.apache.nifi.web.security.jwt.JwtService;
    +import org.apache.nifi.web.security.token.LoginAuthenticationToken;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +import java.io.IOException;
    +import java.net.URI;
    +import java.net.URL;
    +import java.util.Calendar;
    +import java.util.Date;
    +import java.util.List;
    +import java.util.concurrent.TimeUnit;
    +
    +import static com.nimbusds.openid.connect.sdk.claims.UserInfo.EMAIL_CLAIM_NAME;
    +
    +/**
    + * OidcProvider for managing the OpenId Connect Authorization flow.
    + */
    +public class StandardOidcIdentityProvider implements OidcIdentityProvider {
    +
    +    private static final Logger logger = LoggerFactory.getLogger(StandardOidcIdentityProvider.class);
    +
    +    private NiFiProperties properties;
    +    private JwtService jwtService;
    +    private OIDCProviderMetadata oidcProviderMetadata;
    +    private int oidcConnectTimeout;
    +    private int oidcReadTimeout;
    +    private IDTokenValidator tokenValidator;
    +    private ClientID clientId;
    +    private Secret clientSecret;
    +
    +    /**
    +     * Creates a new StandardOidcIdentityProvider.
    +     *
    +     * @param jwtService jwt service
    +     * @param properties properties
    +     */
    +    public StandardOidcIdentityProvider(final JwtService jwtService, final NiFiProperties
properties) {
    +        this.properties = properties;
    +        this.jwtService = jwtService;
    +
    +        // attempt to process the oidc configuration if configured
    +        if (properties.isOidcEnabled()) {
    +            // oidc connect timeout
    +            final String rawConnectTimeout = properties.getOidcConnectTimeout();
    +            try {
    +                oidcConnectTimeout = (int) FormatUtils.getTimeDuration(rawConnectTimeout,
TimeUnit.MILLISECONDS);
    +            } catch (final Exception e) {
    +                logger.warn("Failed to parse value of property '{}' as a valid time period.
Value was '{}'. Ignoring this value and using the default value of '{}'",
    +                        NiFiProperties.SECURITY_USER_OIDC_CONNECT_TIMEOUT, rawConnectTimeout,
NiFiProperties.DEFAULT_SECURITY_USER_OIDC_CONNECT_TIMEOUT);
    +                oidcConnectTimeout = (int) FormatUtils.getTimeDuration(NiFiProperties.DEFAULT_SECURITY_USER_OIDC_CONNECT_TIMEOUT,
TimeUnit.MILLISECONDS);
    +            }
    +
    +            // oidc read timeout
    +            final String rawReadTimeout = properties.getOidcReadTimeout();
    +            try {
    +                oidcReadTimeout = (int) FormatUtils.getTimeDuration(rawReadTimeout, TimeUnit.MILLISECONDS);
    +            } catch (final Exception e) {
    +                logger.warn("Failed to parse value of property '{}' as a valid time period.
Value was '{}'. Ignoring this value and using the default value of '{}'",
    +                        NiFiProperties.SECURITY_USER_OIDC_READ_TIMEOUT, rawReadTimeout,
NiFiProperties.DEFAULT_SECURITY_USER_OIDC_READ_TIMEOUT);
    +                oidcReadTimeout = (int) FormatUtils.getTimeDuration(NiFiProperties.DEFAULT_SECURITY_USER_OIDC_READ_TIMEOUT,
TimeUnit.MILLISECONDS);
    +            }
    +
    +            // client id
    +            final String rawClientId = properties.getOidcClientId();
    +            if (StringUtils.isBlank(rawClientId)) {
    +                throw new RuntimeException("Client ID is required when configuring an
OIDC Provider.");
    +            }
    +            clientId = new ClientID(rawClientId);
    +
    +            // client secret
    +            final String rawClientSecret = properties.getOidcClientSecret();
    +            if (StringUtils.isBlank(rawClientSecret)) {
    +                throw new RuntimeException("Client secret is required when configured
an OIDC Provider.");
    +            }
    +            clientSecret = new Secret(rawClientSecret);
    +
    +            try {
    +                // retrieve the oidc provider metadata
    +                oidcProviderMetadata = retrieveOidcProviderMetadata(properties.getOidcDiscoveryUrl());
    +            } catch (IOException | ParseException e) {
    +                throw new RuntimeException("Unable to retrieve OpenId Connect Provider
metadata from: " + properties.getOidcDiscoveryUrl(), e);
    +            }
    +
    +            // ensure the oidc provider supports basic or post client auth
    +            final List<ClientAuthenticationMethod> clientAuthenticationMethods
= oidcProviderMetadata.getTokenEndpointAuthMethods();
    +            if (clientAuthenticationMethods == null
    +                    || (!clientAuthenticationMethods.contains(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
    +                    && !clientAuthenticationMethods.contains(ClientAuthenticationMethod.CLIENT_SECRET_POST)))
{
    +
    +                throw new RuntimeException(String.format("OpenId Connect Provider does
not support %s or %s",
    +                        ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue(),
    +                        ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()));
    +            }
    +
    +            // extract the supported json web signature algorithms
    +            final List<JWSAlgorithm> allowedAlgorithms = oidcProviderMetadata.getIDTokenJWSAlgs();
    +            if (allowedAlgorithms == null || allowedAlgorithms.isEmpty()) {
    +                throw new RuntimeException("The OpenId Connect Provider does not support
any JWS algorithms.");
    +            }
    +
    +            try {
    +                // get the preferred json web signature algorithm
    +                final String rawPreferredJwsAlgorithm = properties.getOidcPreferredJwsAlgorithm();
    +
    +                final JWSAlgorithm preferredJwsAlgorithm;
    +                if (StringUtils.isBlank(rawPreferredJwsAlgorithm)) {
    +                    preferredJwsAlgorithm = oidcProviderMetadata.getIDTokenJWSAlgs().get(0);
    +                } else {
    +                    if ("none".equals(rawPreferredJwsAlgorithm)) {
    +                        preferredJwsAlgorithm = null;
    +                    } else {
    +                        preferredJwsAlgorithm = JWSAlgorithm.parse(rawPreferredJwsAlgorithm);
    +                    }
    +                }
    +
    +                if (preferredJwsAlgorithm == null) {
    +                    tokenValidator = new IDTokenValidator(oidcProviderMetadata.getIssuer(),
clientId);
    +                } else if (JWSAlgorithm.HS256.equals(preferredJwsAlgorithm) || JWSAlgorithm.HS384.equals(preferredJwsAlgorithm)
|| JWSAlgorithm.HS512.equals(preferredJwsAlgorithm)) {
    +                    tokenValidator = new IDTokenValidator(oidcProviderMetadata.getIssuer(),
clientId, preferredJwsAlgorithm, clientSecret);
    +                } else {
    +                    final ResourceRetriever retriever = new DefaultResourceRetriever(oidcConnectTimeout,
oidcReadTimeout);
    +                    tokenValidator = new IDTokenValidator(oidcProviderMetadata.getIssuer(),
clientId, preferredJwsAlgorithm, oidcProviderMetadata.getJWKSetURI().toURL(), retriever);
    +                }
    +            } catch (final Exception e) {
    +                throw new RuntimeException("Unable to create the ID token validator for
the configured OpenId Connect Provider: " + e.getMessage(), e);
    +            }
    +        }
    +    }
    +
    +    private OIDCProviderMetadata retrieveOidcProviderMetadata(final String discoveryUri)
throws IOException, ParseException {
    +        final URL url = new URL(discoveryUri);
    +        final HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.GET, url);
    +        httpRequest.setConnectTimeout(oidcConnectTimeout);
    +        httpRequest.setReadTimeout(oidcReadTimeout);
    +
    +        final HTTPResponse httpResponse = httpRequest.send();
    +
    +        if (httpResponse.getStatusCode() != 200) {
    +            throw new IOException("Unable to download OpenId Connect Provider metadata
from " + url + ": Status code " + httpResponse.getStatusCode());
    +        }
    +
    +        final JSONObject jsonObject = httpResponse.getContentAsJSONObject();
    +        return OIDCProviderMetadata.parse(jsonObject);
    +    }
    +
    +    @Override
    +    public boolean isOidcEnabled() {
    +        return properties.isOidcEnabled();
    +    }
    +
    +    @Override
    +    public URI getAuthorizationEndpoint() {
    +        if (!isOidcEnabled()) {
    +            throw new IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
    +        }
    +
    +        return oidcProviderMetadata.getAuthorizationEndpointURI();
    +    }
    +
    +    @Override
    +    public Scope getScope() {
    +        if (!isOidcEnabled()) {
    +            throw new IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
    +        }
    +
    +        final Scope scope = new Scope("openid");
    +
    +        // if this provider supports email scope, include it to prevent a subsequent
request to the user endpoint
    +        if (oidcProviderMetadata.getScopes() != null && oidcProviderMetadata.getScopes().contains(OIDCScopeValue.EMAIL))
{
    +            scope.add("email");
    +        }
    +        return scope;
    +    }
    +
    +    @Override
    +    public ClientID getClientId() {
    +        if (!isOidcEnabled()) {
    +            throw new IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
    +        }
    +
    +        return clientId;
    +    }
    +
    +    @Override
    +    public String exchangeAuthorizationCode(final AuthorizationGrant authorizationGrant)
throws IOException {
    +        if (!isOidcEnabled()) {
    +            throw new IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
    +        }
    +
    +        final ClientAuthentication clientAuthentication;
    +        if (oidcProviderMetadata.getTokenEndpointAuthMethods().contains(ClientAuthenticationMethod.CLIENT_SECRET_POST))
{
    +            clientAuthentication = new ClientSecretPost(clientId, clientSecret);
    +        } else {
    +            clientAuthentication = new ClientSecretBasic(clientId, clientSecret);
    +        }
    +
    +        try {
    +            // build the token request
    +            final TokenRequest request = new TokenRequest(oidcProviderMetadata.getTokenEndpointURI(),
clientAuthentication, authorizationGrant, getScope());
    +            final HTTPRequest tokenHttpRequest = request.toHTTPRequest();
    +            tokenHttpRequest.setConnectTimeout(oidcConnectTimeout);
    +            tokenHttpRequest.setReadTimeout(oidcReadTimeout);
    +
    +            // get the token response
    +            final TokenResponse response = OIDCTokenResponseParser.parse(tokenHttpRequest.send());
    +
    +            if (response.indicatesSuccess()) {
    +                final OIDCTokenResponse oidcTokenResponse = (OIDCTokenResponse) response;
    +                final OIDCTokens oidcTokens = oidcTokenResponse.getOIDCTokens();
    +                final JWT oidcJwt = oidcTokens.getIDToken();
    +
    +                // validate the token - no nonce required for authorization code flow
    +                final IDTokenClaimsSet claimsSet = tokenValidator.validate(oidcJwt, null);
    +
    +                // attempt to extract the email from the id token if possible
    +                String email = claimsSet.getStringClaim(EMAIL_CLAIM_NAME);
    +                if (email == null) {
    --- End diff --
    
    May want to use `StringUtils.isBlank()` here as well.


> Add OpenId Connect support for authenticating users
> ---------------------------------------------------
>
>                 Key: NIFI-4210
>                 URL: https://issues.apache.org/jira/browse/NIFI-4210
>             Project: Apache NiFi
>          Issue Type: Improvement
>          Components: Core Framework, Core UI
>            Reporter: Matt Gilman
>            Assignee: Matt Gilman
>
> Add support for authenticating users with the OpenId Connection specification. Evaluate
whether a new extension point is necessary to allow for a given provider to supply custom
code for instance to implement custom token validation.



--
This message was sent by Atlassian JIRA
(v6.4.14#64029)

Mime
View raw message