From 8442f87bb0528ed25ce4d79ce7e7f1d7bf94c1e9 Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Tue, 25 Jul 2017 10:07:24 -0400 Subject: [PATCH 01/11] NIFI-4246 - Client Credentials Grant based OAuth2 Controller Service --- nifi-assembly/NOTICE | 5 + nifi-assembly/pom.xml | 12 + .../nifi-oauth-api-nar/pom.xml | 46 ++++ .../nifi-oauth-bundle/nifi-oauth-api/pom.xml | 35 +++ .../nifi/oauth/OAuth2ClientService.java | 33 +++ .../nifi-oauth-bundle/nifi-oauth-nar/pom.xml | 47 ++++ .../nifi-oauth-bundle/nifi-oauth/pom.xml | 64 +++++ .../oauth/AbstractOAuthControllerService.java | 172 ++++++++++++ ...ientCredentialsGrantControllerService.java | 168 ++++++++++++ .../httpclient/OAuthHTTPConnectionClient.java | 252 ++++++++++++++++++ ...g.apache.nifi.controller.ControllerService | 15 ++ nifi-nar-bundles/nifi-oauth-bundle/pom.xml | 37 +++ .../nifi-standard-processors/pom.xml | 10 + .../nifi/processors/standard/InvokeHTTP.java | 68 ++++- .../src/main/webapp/WEB-INF/jsp/authorize.jsp | 0 nifi-nar-bundles/pom.xml | 13 +- 16 files changed, 966 insertions(+), 11 deletions(-) create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/src/main/java/org/apache/nifi/oauth/OAuth2ClientService.java create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService create mode 100644 nifi-nar-bundles/nifi-oauth-bundle/pom.xml create mode 100644 nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/authorize.jsp diff --git a/nifi-assembly/NOTICE b/nifi-assembly/NOTICE index 20078cc236a5..4777235f1f1c 100644 --- a/nifi-assembly/NOTICE +++ b/nifi-assembly/NOTICE @@ -156,6 +156,11 @@ The following binary components are provided under the Apache Software License v This project contains annotations derived from JCIP-ANNOTATIONS Copyright (c) 2005 Brian Goetz and Tim Peierls. See http://www.jcip.net + (ASLv2) Apache Oltu + The following NOTICE information applies: + Apache Oltu + Copyright 2010-2013 The Apache Software Foundation + (ASLv2) Apache Jakarta HttpClient The following NOTICE information applies: Apache Jakarta HttpClient diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml index ddefcdd5c335..1ec7e7e1af70 100755 --- a/nifi-assembly/pom.xml +++ b/nifi-assembly/pom.xml @@ -673,6 +673,18 @@ language governing permissions and limitations under the License. --> 1.6.0-SNAPSHOT nar + + org.apache.nifi + nifi-oauth-api-nar + 1.4.0-SNAPSHOT + nar + + + org.apache.nifi + nifi-oauth-nar + 1.4.0-SNAPSHOT + nar + diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml new file mode 100644 index 000000000000..83458d497cda --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + + org.apache.nifi + nifi-oauth-bundle + 1.4.0-SNAPSHOT + + + nifi-oauth-api-nar + 1.4.0-SNAPSHOT + nar + + true + true + + + + + org.apache.nifi + nifi-standard-services-api-nar + nar + + + org.apache.nifi + nifi-oauth-api + 1.4.0-SNAPSHOT + + + + diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml new file mode 100644 index 000000000000..95324746ac05 --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml @@ -0,0 +1,35 @@ + + + + 4.0.0 + + + org.apache.nifi + nifi-oauth-bundle + 1.4.0-SNAPSHOT + + + nifi-oauth-api + jar + + + + org.apache.nifi + nifi-api + provided + + + diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/src/main/java/org/apache/nifi/oauth/OAuth2ClientService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/src/main/java/org/apache/nifi/oauth/OAuth2ClientService.java new file mode 100644 index 000000000000..e3b1e3e2ac79 --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/src/main/java/org/apache/nifi/oauth/OAuth2ClientService.java @@ -0,0 +1,33 @@ +package org.apache.nifi.oauth; + +import org.apache.nifi.controller.ControllerService; + +/** + * 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. + *

+ * Created on 7/25/17. + */ + + +public interface OAuth2ClientService + extends ControllerService { + + boolean isOAuthTokenExpired(); + + String getAccessToken(); + + boolean authenticate(); +} diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml new file mode 100644 index 000000000000..3d4ab56cb3a4 --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml @@ -0,0 +1,47 @@ + + + + 4.0.0 + + + org.apache.nifi + nifi-oauth-bundle + 1.4.0-SNAPSHOT + + + nifi-oauth-nar + 1.4.0-SNAPSHOT + nar + + true + true + + + + + org.apache.nifi + nifi-oauth-api-nar + 1.4.0-SNAPSHOT + nar + + + org.apache.nifi + nifi-oauth + 1.4.0-SNAPSHOT + + + + diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml new file mode 100644 index 000000000000..e2594563f7f7 --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml @@ -0,0 +1,64 @@ + + + + 4.0.0 + + + org.apache.nifi + nifi-oauth-bundle + 1.4.0-SNAPSHOT + + + nifi-oauth + jar + + + + org.apache.nifi + nifi-oauth-api + 1.4.0-SNAPSHOT + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.client + 1.0.2 + + + org.apache.nifi + nifi-api + provided + + + org.apache.nifi + nifi-processor-utils + + + org.apache.nifi + nifi-mock + test + + + org.slf4j + slf4j-simple + test + + + junit + junit + test + + + diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java new file mode 100644 index 000000000000..5a4fd31b7f6c --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java @@ -0,0 +1,172 @@ +/* + * 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.oauth; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.AbstractControllerService; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.reporting.InitializationException; + + +public abstract class AbstractOAuthControllerService + extends AbstractControllerService implements OAuth2ClientService { + + protected String accessToken = null; + protected String refreshToken = null; + protected String tokenType = null; + protected long expiresIn = -1; + protected long expiresTime = -1; + protected long lastResponseTimestamp = -1; + protected Map extraHeaders = new HashMap(); + protected String authUrl = null; + protected long expireTimeSafetyNetSeconds = -1; + protected String accessTokenRespName = null; + protected String expireTimeRespName = null; + protected String expireInRespName = null; + protected String tokenTypeRespName = null; + protected String scopeRespName = null; + + public static final PropertyDescriptor AUTH_SERVER_URL = new PropertyDescriptor + .Builder().name("OAuth2 Authorization Server URL") + .displayName("OAuth2 Authorization Server") + .description("OAuth2 Authorization Server that grants access to the protected resources on the behalf of the resource owner.") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor RESPONSE_ACCESS_TOKEN_FIELD_NAME = new PropertyDescriptor + .Builder().name("JSON response 'access_token' name") + .displayName("JSON response 'access_token' name") + .description("Name of the field in the JSON response that contains the access token. IETF OAuth2 spec default is 'access_token' if your API provider's" + + " response field is different this is where you can change that.") + .defaultValue("access_token") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor RESPONSE_EXPIRE_TIME_FIELD_NAME = new PropertyDescriptor + .Builder().name("JSON response 'expire_time' name") + .displayName("JSON response 'expire_time' name") + .description("Name of the field in the JSON response that contains the expire time. IETF OAuth2 spec default is 'expire_time' if your API provider's" + + " response field is different this is where you can change that.") + .defaultValue("expire_time") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor RESPONSE_EXPIRE_IN_FIELD_NAME = new PropertyDescriptor + .Builder().name("JSON response 'expire_in' name") + .displayName("JSON response 'expire_in' name") + .description("Name of the field in the JSON response that contains the expire in. IETF OAuth2 spec default is 'expire_in' if your API provider's" + + " response field is different this is where you can change that.") + .defaultValue("expire_in") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor RESPONSE_TOKEN_TYPE_FIELD_NAME = new PropertyDescriptor + .Builder().name("JSON response 'token_type' name") + .displayName("JSON response 'token_type' name") + .description("Name of the field in the JSON response that contains the token type. IETF OAuth2 spec default is 'token_type' if your API provider's" + + " response field is different this is where you can change that.") + .defaultValue("token_type") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor RESPONSE_SCOPE_FIELD_NAME = new PropertyDescriptor + .Builder().name("JSON response 'scope' name") + .displayName("JSON response 'scope' name") + .description("Name of the field in the JSON response that contains the scope. IETF OAuth2 spec default is 'scope' if your API provider's" + + " response field is different this is where you can change that.") + .defaultValue("scope") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor EXPIRE_TIME_SAFETY_NET = new PropertyDescriptor + .Builder().name("Expire time safety net in seconds") + .displayName("Expire time safety net in seconds") + .description("There can be a chance that the authentication server and the NiFi agent clocks could be out of sync. Expires In is an OAuth standard " + + "that tells when the access token received will be invalidated. Comparing the current system clock to that value and understanding if the " + + "access token is still valid can be achieved this way. However since the clocks can be out of sync with the authentication server " + + "this property ensures that the access token can be refreshed at a configurable interval before it actual expires to ensure no downtime " + + "waiting on new tokens from the authentication server.") + .defaultValue("10") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + protected void onEnabled(final ConfigurationContext context) + throws InitializationException { + Map allProperties = context.getProperties(); + Iterator itr = allProperties.keySet().iterator(); + while (itr.hasNext()) { + PropertyDescriptor pd = itr.next(); + if (pd.isDynamic()) { + extraHeaders.put(pd.getName(), allProperties.get(pd)); + } + } + + this.authUrl = context.getProperty(AUTH_SERVER_URL).getValue(); + this.expireTimeSafetyNetSeconds = new Long(context.getProperty(EXPIRE_TIME_SAFETY_NET).getValue()); + + // Name of the OAuth2 spec fields that should be extracted from the JSON authentication response. While in theory every provider should be + // using the same names in the response JSON that rarely happens. These values are used to ensure that if the provider does NOT follow the + // spec the user can still use this integration approach instead of having to write something custom. + this.accessTokenRespName = context.getProperty(RESPONSE_ACCESS_TOKEN_FIELD_NAME).getValue(); + this.expireTimeRespName = context.getProperty(RESPONSE_EXPIRE_TIME_FIELD_NAME).getValue(); + this.expireInRespName = context.getProperty(RESPONSE_EXPIRE_IN_FIELD_NAME).getValue(); + this.tokenTypeRespName = context.getProperty(RESPONSE_TOKEN_TYPE_FIELD_NAME).getValue(); + this.scopeRespName = context.getProperty(RESPONSE_SCOPE_FIELD_NAME).getValue(); + } + + public boolean isOAuthTokenExpired() { + if (expiresTime == -1 && expiresIn == -1) + return true; + + if (expiresTime > 0) { + // Use the actual time that the token will authenticate + if ((expiresTime - (expireTimeSafetyNetSeconds * 1000)) <= System.currentTimeMillis()) { + return true; + } else { + return false; + } + } else if (expiresIn > 0) { + long tombStoneTS = ((lastResponseTimestamp + (expiresIn * 1000)) - (expireTimeSafetyNetSeconds * 1000)); + if (System.currentTimeMillis() >= tombStoneTS) { + return true; + } else { + return false; + } + } else { + getLogger().warn("Neither 'expire_in' nor 'expire_time' is set. This makes it impossible to determine if the access token in still valid" + + " or not. Assuming the token is valid."); + return false; + } + } + + public String getAccessToken() { + return this.accessToken; + } +} diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java new file mode 100644 index 000000000000..119a9b44806b --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java @@ -0,0 +1,168 @@ +/** + * 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. + *

+ * Created on 7/25/17. + */ + +package org.apache.nifi.oauth; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnDisabled; +import org.apache.nifi.annotation.lifecycle.OnEnabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.oauth.httpclient.OAuthHTTPConnectionClient; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.reporting.InitializationException; +import org.apache.oltu.oauth2.client.HttpClient; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.message.types.GrantType; + +@Tags({ "oauth2", "client", "secret", "post"}) +@CapabilityDescription("POSTs the ClientId and ClientSecret to the OAuth2 authentication server to retrieve the" + + " access token. The access token is stored locally in the controller service and used by processors " + + "referencing this controller service.") +public class OAuth2ClientCredentialsGrantControllerService + extends AbstractOAuthControllerService + implements OAuth2ClientService { + + public static final PropertyDescriptor CLIENT_ID = new PropertyDescriptor + .Builder().name("OAuth2 Client ID") + .displayName("OAuth2 Client ID") + .description("OAuth2 Client ID passed to the authorization server") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor CLIENT_SECRET = new PropertyDescriptor + .Builder().name("OAuth2 Client Secret") + .displayName("OAuth2 Client Secret") + .description("OAuth2 Client Secret that will be passed to the authorization server in exchange for an access token") + .sensitive(true) + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + private static final List properties; + + static { + final List props = new ArrayList<>(); + props.add(AUTH_SERVER_URL); + props.add(CLIENT_ID); + props.add(CLIENT_SECRET); + props.add(RESPONSE_ACCESS_TOKEN_FIELD_NAME); + props.add(RESPONSE_EXPIRE_TIME_FIELD_NAME); + props.add(RESPONSE_EXPIRE_IN_FIELD_NAME); + props.add(RESPONSE_SCOPE_FIELD_NAME); + props.add(RESPONSE_TOKEN_TYPE_FIELD_NAME); + props.add(EXPIRE_TIME_SAFETY_NET); + properties = Collections.unmodifiableList(props); + } + + @Override + protected List getSupportedPropertyDescriptors() { + return properties; + } + + private String clientId = null; + private String clientSecret = null; + private Base64.Encoder enc = Base64.getEncoder(); + + /** + * @param context the configuration context + * @throws InitializationException if unable to create a database connection + */ + @OnEnabled + public void onEnabled(final ConfigurationContext context) throws InitializationException { + + super.onEnabled(context); + + clientId = context.getProperty(CLIENT_ID).getValue(); + clientSecret = context.getProperty(CLIENT_SECRET).getValue(); + } + + public boolean authenticate() { + HttpClient con = null; + + try { + + String base64String = enc.encodeToString((clientId + ":" + clientSecret).getBytes()); + + OAuthClientRequest.TokenRequestBuilder authTokenRequest = + new OAuthClientRequest.TokenRequestBuilder(authUrl) + .setClientId(clientId) + .setClientSecret(clientSecret) + .setGrantType(GrantType.CLIENT_CREDENTIALS); + + OAuthClientRequest headerRequest = authTokenRequest.buildHeaderMessage(); + headerRequest.setHeader("Cache-Control", "no-cache"); + headerRequest.setHeader("Content-Type", "application/x-www-form-urlencoded"); + headerRequest.setHeader("Authorization", "Basic " + base64String); + + // Add the dynamic headers specified by the user + if (extraHeaders != null && extraHeaders.size() > 0) { + Iterator itr = extraHeaders.keySet().iterator(); + while (itr.hasNext()) { + String key = itr.next(); + headerRequest.setHeader(key, extraHeaders.get(key)); + } + } + + headerRequest.setBody("grant_type=client_credentials"); + + con = new OAuthHTTPConnectionClient(accessTokenRespName, tokenTypeRespName, scopeRespName, expireInRespName, expireTimeRespName); + OAuthHTTPConnectionClient.CustomOAuthAccessTokenResponse authResp = con.execute(headerRequest, null, "POST", OAuthHTTPConnectionClient.CustomOAuthAccessTokenResponse.class); + + this.accessToken = authResp.getAccessToken(); + this.expiresIn = authResp.getExpiresIn(); + this.refreshToken = authResp.getRefreshToken(); + this.tokenType = authResp.getTokenType(); + this.lastResponseTimestamp = System.currentTimeMillis(); + + if (getLogger().isDebugEnabled()) { + getLogger().debug("Local variables set and OAuth2 InvokeHTTP processor is now ready for operation with Access Token"); + } + + return true; + + } catch (OAuthProblemException ope) { + getLogger().error(ope.getMessage()); + } catch (OAuthSystemException ose) { + getLogger().error(ose.getMessage()); + } catch (Exception e) { + getLogger().error(e.getMessage()); + } finally { + if (con != null) + con.shutdown(); + } + + return false; + } + + @OnDisabled + public void shutdown() { + + } +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java new file mode 100644 index 000000000000..cc4fd7541d7b --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java @@ -0,0 +1,252 @@ +/* + * 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.oauth.httpclient; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.apache.oltu.oauth2.client.HttpClient; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest; +import org.apache.oltu.oauth2.client.response.OAuthAccessTokenResponse; +import org.apache.oltu.oauth2.client.response.OAuthClientResponse; +import org.apache.oltu.oauth2.common.OAuth; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.token.BasicOAuthToken; +import org.apache.oltu.oauth2.common.token.OAuthToken; +import org.apache.oltu.oauth2.common.utils.OAuthUtils; +import org.json.JSONObject; + +public class OAuthHTTPConnectionClient + implements HttpClient { + + private String accessTokenName = null; + private String tokenTypeName = null; + private String scopeName = null; + private String expireInName = null; + private String expireTimeName = null; + + public OAuthHTTPConnectionClient(String accessTokenName, String tokenTypeName, String scopeName, String expireInName, String expireTimeName) { + this.accessTokenName = accessTokenName; + this.tokenTypeName = tokenTypeName; + this.scopeName = scopeName; + this.expireInName = expireInName; + this.expireTimeName = expireTimeName; + } + + @Override + public T execute(OAuthClientRequest request, Map headers, + String requestMethod, Class responseClass) throws OAuthSystemException, OAuthProblemException { + + InputStream responseBody = null; + URLConnection c; + Map> responseHeaders = new HashMap>(); + int responseCode; + try { + URL url = new URL(request.getLocationUri()); + + c = url.openConnection(); + responseCode = -1; + if (c instanceof HttpURLConnection) { + HttpURLConnection httpURLConnection = (HttpURLConnection) c; + + if (headers != null && !headers.isEmpty()) { + for (Map.Entry header : headers.entrySet()) { + httpURLConnection.addRequestProperty(header.getKey(), header.getValue()); + } + } + + if (request.getHeaders() != null) { + for (Map.Entry header : request.getHeaders().entrySet()) { + httpURLConnection.addRequestProperty(header.getKey(), header.getValue()); + } + } + + if (OAuthUtils.isEmpty(requestMethod)) { + httpURLConnection.setRequestMethod(OAuth.HttpMethod.GET); + } else { + httpURLConnection.setRequestMethod(requestMethod); + setRequestBody(request, requestMethod, httpURLConnection); + } + + httpURLConnection.connect(); + + InputStream inputStream; + responseCode = httpURLConnection.getResponseCode(); + if (responseCode == 400 || responseCode == 405 || responseCode == 401 || responseCode == 403) { + inputStream = httpURLConnection.getErrorStream(); + } else { + inputStream = httpURLConnection.getInputStream(); + } + + responseHeaders = httpURLConnection.getHeaderFields(); + responseBody = inputStream; + } + } catch (IOException e) { + throw new OAuthSystemException(e); + } + + CustomOAuthAccessTokenResponse cr = new CustomOAuthAccessTokenResponse(responseBody, c.getContentType(), responseCode, responseHeaders, + accessTokenName, tokenTypeName, scopeName, expireInName, expireTimeName); + + return (T) cr; + } + + private void setRequestBody(OAuthClientRequest request, String requestMethod, HttpURLConnection httpURLConnection) + throws IOException { + String requestBody = request.getBody(); + if (OAuthUtils.isEmpty(requestBody)) { + return; + } + + if (OAuth.HttpMethod.POST.equals(requestMethod) || OAuth.HttpMethod.PUT.equals(requestMethod)) { + httpURLConnection.setDoOutput(true); + OutputStream ost = httpURLConnection.getOutputStream(); + PrintWriter pw = new PrintWriter(ost); + pw.print(requestBody); + pw.flush(); + pw.close(); + } + } + + @Override + public void shutdown() { + // Nothing to do here + } + + + public static class CustomOAuthAccessTokenResponse + extends OAuthAccessTokenResponse { + + // Names of the fields that should be pulled from the JSON response. + private String accessTokenName = null; + private String tokenTypeName = null; + private String scopeName = null; + private String expireInName = null; + private String expireTimeName = null; + + private String accessToken; + private int responseCode; + private String tokenType; + private long expireTime; + private long expiresIn; + private String scope; + + public CustomOAuthAccessTokenResponse(InputStream responseBody, String contentType, int responseCode, + Map> responseHeaders, String accessTokenName, String tokenTypeName, String scopeName, + String expireInName, String expireTimeName) throws OAuthProblemException { + + // Set the field names + this.accessTokenName = accessTokenName; + this.tokenTypeName = tokenTypeName; + this.scopeName = scopeName; + this.expireInName = expireInName; + this.expireTimeName = expireTimeName; + + setResponseCode(responseCode); + this.responseCode = responseCode; + setContentType(contentType); + this.contentType = contentType; + setHeaders(responseHeaders); + + String loginResponseBody = null; + + if (responseBody != null) { + StringWriter writer = new StringWriter(); + try { + IOUtils.copy(responseBody, writer, "UTF-8"); + } catch (IOException e) { + throw OAuthProblemException.error("Invalid response body received from access token request", e.getMessage()); + } + loginResponseBody = writer.toString(); + JSONObject json = new JSONObject(loginResponseBody); + + if (json.has(accessTokenName)) { + this.accessToken = json.getString(accessTokenName); + } + + if (json.has(tokenTypeName)) { + this.tokenType = json.getString(tokenTypeName); + } + + if (json.has(expireTimeName)) { + this.expireTime = json.getLong(expireTimeName); + } + + if (json.has(expireInName)) { + this.expiresIn = json.getLong(expireInName); + } + + if (json.has(scopeName)) { + this.scope = json.getString(scopeName); + } + } + } + + @Override + public String getAccessToken() { + return this.accessToken; + } + + @Override + public String getTokenType() { + return this.tokenType; + } + + @Override + public Long getExpiresIn() { + return this.expiresIn; + } + + @Override + public String getRefreshToken() { + return null; + } + + @Override + public String getScope() { + return this.scope; + } + + @Override + public OAuthToken getOAuthToken() { + BasicOAuthToken oAuthToken = new BasicOAuthToken(getAccessToken(), getTokenType(), getExpiresIn(), getRefreshToken(), getScope()); + return oAuthToken; + } + + @Override + protected void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + protected void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + } +} diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService new file mode 100644 index 000000000000..6da79f7179bf --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.oauth.OAuth2ClientCredentialsGrantControllerService \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-oauth-bundle/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/pom.xml new file mode 100644 index 000000000000..a01a3ff91012 --- /dev/null +++ b/nifi-nar-bundles/nifi-oauth-bundle/pom.xml @@ -0,0 +1,37 @@ + + + + 4.0.0 + + + org.apache.nifi + nifi-nar-bundles + 1.4.0-SNAPSHOT + + + org.apache.nifi + nifi-oauth-bundle + 1.4.0-SNAPSHOT + pom + + + nifi-oauth-api + nifi-oauth-api-nar + nifi-oauth + nifi-oauth-nar + + + diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml index e9e2a1a17228..34d50d3b34cb 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml @@ -24,6 +24,16 @@ nifi-api provided + + org.apache.nifi + nifi-oauth-api + 1.6.0-SNAPSHOT + + + org.apache.nifi + nifi-oauth + 1.6.0-SNAPSHOT + org.apache.nifi nifi-processor-utils diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java index 0f0f87876917..b615787d8b4d 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java @@ -31,6 +31,37 @@ import okhttp3.ResponseBody; import okhttp3.internal.tls.OkHostnameVerifier; import okio.BufferedSink; +import static org.apache.commons.lang3.StringUtils.trimToEmpty; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Proxy.Type; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; + import org.apache.commons.io.input.TeeInputStream; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.annotation.behavior.DynamicProperty; @@ -49,6 +80,7 @@ import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.oauth.OAuth2ClientService; import org.apache.nifi.processor.AbstractProcessor; import org.apache.nifi.processor.DataUnit; import org.apache.nifi.processor.ProcessContext; @@ -63,7 +95,6 @@ import org.apache.nifi.stream.io.StreamUtils; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; - import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -141,7 +172,6 @@ public final class InvokeHTTP extends AbstractProcessor { public final static String EXCEPTION_CLASS = "invokehttp.java.exception.class"; public final static String EXCEPTION_MESSAGE = "invokehttp.java.exception.message"; - public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; // Set of flowfile attributes which we generally always ignore during @@ -393,6 +423,13 @@ public final class InvokeHTTP extends AbstractProcessor { .allowableValues("true", "false") .build(); + public static final PropertyDescriptor PROP_OAUTH_CLIENT_SERVICE = new PropertyDescriptor.Builder() + .name("OAuth2 Controller Service") + .description("The OAuth2 controller service which contains the valid credentials for this processor to use.") + .required(false) + .identifiesControllerService(OAuth2ClientService.class) + .build(); + public static final PropertyDescriptor PROP_USE_ETAG = new PropertyDescriptor.Builder() .name("use-etag") .description("Enable HTTP entity tag (ETag) support for HTTP requests.") @@ -438,7 +475,8 @@ public final class InvokeHTTP extends AbstractProcessor { PROP_USE_CHUNKED_ENCODING, PROP_PENALIZE_NO_RETRY, PROP_USE_ETAG, - PROP_ETAG_MAX_CACHE_SIZE)); + PROP_ETAG_MAX_CACHE_SIZE, + PROP_OAUTH_CLIENT_SERVICE)); // relationships public static final Relationship REL_SUCCESS_REQ = new Relationship.Builder() @@ -938,8 +976,28 @@ private Request configureRequest(final ProcessContext context, final ProcessSess requestBuilder = requestBuilder.url(url); final String authUser = trimToEmpty(context.getProperty(PROP_BASIC_AUTH_USERNAME).getValue()); - // If the username/password properties are set then check if digest auth is being used - if (!authUser.isEmpty() && "false".equalsIgnoreCase(context.getProperty(PROP_DIGEST_AUTH).getValue())) { + // Handle OAuth2 authentication if configured. + if (context.getProperty(PROP_OAUTH_CLIENT_SERVICE).isSet()) { + OAuth2ClientService oaCs = + context.getProperty(PROP_OAUTH_CLIENT_SERVICE).asControllerService(OAuth2ClientService.class); + + String accessToken = null; + if (!oaCs.isOAuthTokenExpired()) { + accessToken = oaCs.getAccessToken(); + } else { + // Attempt a re-authentication. + boolean authenticationSucceded = oaCs.authenticate(); + if (authenticationSucceded) { + accessToken = oaCs.getAccessToken(); + } + } + + if (accessToken != null) { + String oauthAccessToken = "Bearer " + accessToken; + requestBuilder = requestBuilder.header("Authorization", oauthAccessToken); + } + + } else if (!authUser.isEmpty() && "false".equalsIgnoreCase(context.getProperty(PROP_DIGEST_AUTH).getValue())) { final String authPass = trimToEmpty(context.getProperty(PROP_BASIC_AUTH_PASSWORD).getValue()); String credential = Credentials.basic(authUser, authPass); diff --git a/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/authorize.jsp b/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/authorize.jsp new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/nifi-nar-bundles/pom.xml b/nifi-nar-bundles/pom.xml index a1d6eacd7447..61f7aa84e757 100755 --- a/nifi-nar-bundles/pom.xml +++ b/nifi-nar-bundles/pom.xml @@ -58,14 +58,14 @@ nifi-scripting-bundle nifi-elasticsearch-bundle nifi-amqp-bundle - nifi-splunk-bundle + nifi-splunk-bundle nifi-jms-bundle nifi-lumberjack-bundle nifi-beats-bundle nifi-cassandra-bundle nifi-spring-bundle nifi-hive-bundle - nifi-site-to-site-reporting-bundle + nifi-site-to-site-reporting-bundle nifi-mqtt-bundle nifi-evtx-bundle nifi-slack-bundle @@ -93,6 +93,7 @@ nifi-spark-bundle nifi-atlas-bundle nifi-druid-bundle + nifi-oauth-bundle @@ -115,7 +116,7 @@ false 7 true - + buildRevision buildBranch @@ -134,8 +135,8 @@ - - + + @@ -272,4 +273,4 @@ - + \ No newline at end of file From 137158dbd5d5f8fafaba10c3bb50342299b600dd Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Fri, 6 Oct 2017 16:49:35 -0400 Subject: [PATCH 02/11] Rebasing and upping to version 1.5.0-SNAPSHOT from 1.4.0-SNAPSHOT --- nifi-assembly/pom.xml | 4 ++-- .../src/test/resources/nifi/fingerprint/flow2.xml | 2 +- .../src/test/resources/xxe_template.xml | 4 ++-- .../nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml | 6 +++--- nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml | 2 +- nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml | 8 ++++---- nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml | 4 ++-- nifi-nar-bundles/nifi-oauth-bundle/pom.xml | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml index 1ec7e7e1af70..515ad52a9047 100755 --- a/nifi-assembly/pom.xml +++ b/nifi-assembly/pom.xml @@ -676,13 +676,13 @@ language governing permissions and limitations under the License. --> org.apache.nifi nifi-oauth-api-nar - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nar org.apache.nifi nifi-oauth-nar - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nar diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/nifi/fingerprint/flow2.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/nifi/fingerprint/flow2.xml index bab1778ad02d..788726816e5e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/nifi/fingerprint/flow2.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/nifi/fingerprint/flow2.xml @@ -31,7 +31,7 @@ org.apache.nifi nifi-standard-nar - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT 1 0 s diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/xxe_template.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/xxe_template.xml index 82674e0e1a0f..0db297935912 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/xxe_template.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/xxe_template.xml @@ -35,7 +35,7 @@ nifi-standard-nar org.apache.nifi - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT WARN @@ -148,7 +148,7 @@ nifi-standard-nar org.apache.nifi - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT WARN diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml index 83458d497cda..39081f4f80c3 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml @@ -19,11 +19,11 @@ org.apache.nifi nifi-oauth-bundle - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nifi-oauth-api-nar - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nar true @@ -39,7 +39,7 @@ org.apache.nifi nifi-oauth-api - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml index 95324746ac05..1ee12db3c4ab 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml @@ -19,7 +19,7 @@ org.apache.nifi nifi-oauth-bundle - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nifi-oauth-api diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml index 3d4ab56cb3a4..a403191a9f74 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml @@ -19,11 +19,11 @@ org.apache.nifi nifi-oauth-bundle - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nifi-oauth-nar - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nar true @@ -34,13 +34,13 @@ org.apache.nifi nifi-oauth-api-nar - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nar org.apache.nifi nifi-oauth - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml index e2594563f7f7..8fb188b69cb9 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml @@ -19,7 +19,7 @@ org.apache.nifi nifi-oauth-bundle - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT nifi-oauth @@ -29,7 +29,7 @@ org.apache.nifi nifi-oauth-api - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT org.apache.oltu.oauth2 diff --git a/nifi-nar-bundles/nifi-oauth-bundle/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/pom.xml index a01a3ff91012..7deaab7ca6ae 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/pom.xml @@ -19,12 +19,12 @@ org.apache.nifi nifi-nar-bundles - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT org.apache.nifi nifi-oauth-bundle - 1.4.0-SNAPSHOT + 1.5.0-SNAPSHOT pom From 5601c1cac771ce923db2bd1b54b368b83e5cee7d Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Sun, 8 Oct 2017 14:02:44 -0400 Subject: [PATCH 03/11] version introduced --- nifi-assembly/pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml index 515ad52a9047..f5464b4d6692 100755 --- a/nifi-assembly/pom.xml +++ b/nifi-assembly/pom.xml @@ -1,13 +1,13 @@ 4.0.0 @@ -857,10 +857,10 @@ language governing permissions and limitations under the License. --> /opt/nifi/nifi-${project.version}/lib /opt/nifi/nifi-${project.version}/lib From 2f2b6b10a19d6a133645d490f612c48ea6abeb7a Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Mon, 8 Jan 2018 11:59:59 -0500 Subject: [PATCH 04/11] changes suggested by Andy --- .../oauth/AbstractOAuthControllerService.java | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java index 5a4fd31b7f6c..066efa6cb25e 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java @@ -47,25 +47,27 @@ public abstract class AbstractOAuthControllerService protected String scopeRespName = null; public static final PropertyDescriptor AUTH_SERVER_URL = new PropertyDescriptor - .Builder().name("OAuth2 Authorization Server URL") + .Builder().name("oauth2_authorization_server_url") .displayName("OAuth2 Authorization Server") .description("OAuth2 Authorization Server that grants access to the protected resources on the behalf of the resource owner.") .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); public static final PropertyDescriptor RESPONSE_ACCESS_TOKEN_FIELD_NAME = new PropertyDescriptor - .Builder().name("JSON response 'access_token' name") + .Builder().name("JSON_response_access_token_name") .displayName("JSON response 'access_token' name") .description("Name of the field in the JSON response that contains the access token. IETF OAuth2 spec default is 'access_token' if your API provider's" + " response field is different this is where you can change that.") .defaultValue("access_token") .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); public static final PropertyDescriptor RESPONSE_EXPIRE_TIME_FIELD_NAME = new PropertyDescriptor - .Builder().name("JSON response 'expire_time' name") + .Builder().name("JSON_response_access_token_name") .displayName("JSON response 'expire_time' name") .description("Name of the field in the JSON response that contains the expire time. IETF OAuth2 spec default is 'expire_time' if your API provider's" + " response field is different this is where you can change that.") @@ -75,37 +77,40 @@ public abstract class AbstractOAuthControllerService .build(); public static final PropertyDescriptor RESPONSE_EXPIRE_IN_FIELD_NAME = new PropertyDescriptor - .Builder().name("JSON response 'expire_in' name") + .Builder().name("JSON_response_access_token_name") .displayName("JSON response 'expire_in' name") .description("Name of the field in the JSON response that contains the expire in. IETF OAuth2 spec default is 'expire_in' if your API provider's" + " response field is different this is where you can change that.") .defaultValue("expire_in") .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); public static final PropertyDescriptor RESPONSE_TOKEN_TYPE_FIELD_NAME = new PropertyDescriptor - .Builder().name("JSON response 'token_type' name") + .Builder().name("JSON_response_access_token_name") .displayName("JSON response 'token_type' name") .description("Name of the field in the JSON response that contains the token type. IETF OAuth2 spec default is 'token_type' if your API provider's" + " response field is different this is where you can change that.") .defaultValue("token_type") .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); public static final PropertyDescriptor RESPONSE_SCOPE_FIELD_NAME = new PropertyDescriptor - .Builder().name("JSON response 'scope' name") + .Builder().name("JSON_response_scope_name") .displayName("JSON response 'scope' name") .description("Name of the field in the JSON response that contains the scope. IETF OAuth2 spec default is 'scope' if your API provider's" + " response field is different this is where you can change that.") .defaultValue("scope") .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); public static final PropertyDescriptor EXPIRE_TIME_SAFETY_NET = new PropertyDescriptor - .Builder().name("Expire time safety net in seconds") + .Builder().name("expire_time_safety_net") .displayName("Expire time safety net in seconds") .description("There can be a chance that the authentication server and the NiFi agent clocks could be out of sync. Expires In is an OAuth standard " + "that tells when the access token received will be invalidated. Comparing the current system clock to that value and understanding if the " + @@ -114,6 +119,7 @@ public abstract class AbstractOAuthControllerService "waiting on new tokens from the authentication server.") .defaultValue("10") .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); @@ -128,17 +134,17 @@ protected void onEnabled(final ConfigurationContext context) } } - this.authUrl = context.getProperty(AUTH_SERVER_URL).getValue(); - this.expireTimeSafetyNetSeconds = new Long(context.getProperty(EXPIRE_TIME_SAFETY_NET).getValue()); + this.authUrl = context.getProperty(AUTH_SERVER_URL).evaluateAttributeExpressions().getValue(); + this.expireTimeSafetyNetSeconds = new Long(context.getProperty(EXPIRE_TIME_SAFETY_NET).evaluateAttributeExpressions().getValue()); // Name of the OAuth2 spec fields that should be extracted from the JSON authentication response. While in theory every provider should be // using the same names in the response JSON that rarely happens. These values are used to ensure that if the provider does NOT follow the // spec the user can still use this integration approach instead of having to write something custom. - this.accessTokenRespName = context.getProperty(RESPONSE_ACCESS_TOKEN_FIELD_NAME).getValue(); - this.expireTimeRespName = context.getProperty(RESPONSE_EXPIRE_TIME_FIELD_NAME).getValue(); - this.expireInRespName = context.getProperty(RESPONSE_EXPIRE_IN_FIELD_NAME).getValue(); - this.tokenTypeRespName = context.getProperty(RESPONSE_TOKEN_TYPE_FIELD_NAME).getValue(); - this.scopeRespName = context.getProperty(RESPONSE_SCOPE_FIELD_NAME).getValue(); + this.accessTokenRespName = context.getProperty(RESPONSE_ACCESS_TOKEN_FIELD_NAME).evaluateAttributeExpressions().getValue(); + this.expireTimeRespName = context.getProperty(RESPONSE_EXPIRE_TIME_FIELD_NAME).evaluateAttributeExpressions().getValue(); + this.expireInRespName = context.getProperty(RESPONSE_EXPIRE_IN_FIELD_NAME).evaluateAttributeExpressions().getValue(); + this.tokenTypeRespName = context.getProperty(RESPONSE_TOKEN_TYPE_FIELD_NAME).evaluateAttributeExpressions().getValue(); + this.scopeRespName = context.getProperty(RESPONSE_SCOPE_FIELD_NAME).evaluateAttributeExpressions().getValue(); } public boolean isOAuthTokenExpired() { @@ -146,7 +152,11 @@ public boolean isOAuthTokenExpired() { return true; if (expiresTime > 0) { - // Use the actual time that the token will authenticate + // Use the actual clock time that the token will expire + if ((System.currentTimeMillis() + (expireTimeSafetyNetSeconds * 1000)) >= expiresTime) { + return true; + } + if ((expiresTime - (expireTimeSafetyNetSeconds * 1000)) <= System.currentTimeMillis()) { return true; } else { @@ -161,8 +171,8 @@ public boolean isOAuthTokenExpired() { } } else { getLogger().warn("Neither 'expire_in' nor 'expire_time' is set. This makes it impossible to determine if the access token in still valid" + - " or not. Assuming the token is valid."); - return false; + " or not. Assuming the token is invalid."); + return true; } } From 5df8791a9de55bf6725d47ce02848d4f51608bf9 Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Tue, 9 Jan 2018 12:02:54 -0700 Subject: [PATCH 05/11] updated version of jersey-client and jersey-server for OAuth components. --- .../nifi-standard-processors/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml index 34d50d3b34cb..02052a622b92 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml @@ -78,6 +78,16 @@ org.apache.nifi nifi-record + + org.glassfish.jersey.core + jersey-client + 2.26 + + + org.glassfish.jersey.core + jersey-server + 2.26 + commons-io commons-io From 51c185668cd7f2a11e81d439b486846346bde9a1 Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Mon, 5 Mar 2018 14:35:23 -0500 Subject: [PATCH 06/11] Latest updated --- .../oauth/AbstractOAuthControllerService.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java index 066efa6cb25e..5f3fb99d1ca8 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java @@ -73,6 +73,7 @@ public abstract class AbstractOAuthControllerService " response field is different this is where you can change that.") .defaultValue("expire_time") .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); @@ -109,20 +110,6 @@ public abstract class AbstractOAuthControllerService .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); - public static final PropertyDescriptor EXPIRE_TIME_SAFETY_NET = new PropertyDescriptor - .Builder().name("expire_time_safety_net") - .displayName("Expire time safety net in seconds") - .description("There can be a chance that the authentication server and the NiFi agent clocks could be out of sync. Expires In is an OAuth standard " + - "that tells when the access token received will be invalidated. Comparing the current system clock to that value and understanding if the " + - "access token is still valid can be achieved this way. However since the clocks can be out of sync with the authentication server " + - "this property ensures that the access token can be refreshed at a configurable interval before it actual expires to ensure no downtime " + - "waiting on new tokens from the authentication server.") - .defaultValue("10") - .required(true) - .expressionLanguageSupported(true) - .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) - .build(); - protected void onEnabled(final ConfigurationContext context) throws InitializationException { Map allProperties = context.getProperties(); @@ -135,7 +122,6 @@ protected void onEnabled(final ConfigurationContext context) } this.authUrl = context.getProperty(AUTH_SERVER_URL).evaluateAttributeExpressions().getValue(); - this.expireTimeSafetyNetSeconds = new Long(context.getProperty(EXPIRE_TIME_SAFETY_NET).evaluateAttributeExpressions().getValue()); // Name of the OAuth2 spec fields that should be extracted from the JSON authentication response. While in theory every provider should be // using the same names in the response JSON that rarely happens. These values are used to ensure that if the provider does NOT follow the @@ -153,17 +139,17 @@ public boolean isOAuthTokenExpired() { if (expiresTime > 0) { // Use the actual clock time that the token will expire - if ((System.currentTimeMillis() + (expireTimeSafetyNetSeconds * 1000)) >= expiresTime) { + if (System.currentTimeMillis() >= expiresTime) { return true; } - if ((expiresTime - (expireTimeSafetyNetSeconds * 1000)) <= System.currentTimeMillis()) { + if (expiresTime <= System.currentTimeMillis()) { return true; } else { return false; } } else if (expiresIn > 0) { - long tombStoneTS = ((lastResponseTimestamp + (expiresIn * 1000)) - (expireTimeSafetyNetSeconds * 1000)); + long tombStoneTS = ((lastResponseTimestamp + (expiresIn * 1000))); if (System.currentTimeMillis() >= tombStoneTS) { return true; } else { From 1823242ca889d335c7cf8b7f654ab8c8c256d05f Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Tue, 6 Mar 2018 20:21:43 -0500 Subject: [PATCH 07/11] build issues --- .../oauth/OAuth2ClientCredentialsGrantControllerService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java index 119a9b44806b..45568a5bb27b 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java @@ -77,7 +77,6 @@ public class OAuth2ClientCredentialsGrantControllerService props.add(RESPONSE_EXPIRE_IN_FIELD_NAME); props.add(RESPONSE_SCOPE_FIELD_NAME); props.add(RESPONSE_TOKEN_TYPE_FIELD_NAME); - props.add(EXPIRE_TIME_SAFETY_NET); properties = Collections.unmodifiableList(props); } From 6a80063313f9c8e03a1f42ae84813cabd5fc8349 Mon Sep 17 00:00:00 2001 From: Andy LoPresto Date: Wed, 14 Mar 2018 13:29:00 -0700 Subject: [PATCH 08/11] NIFI-4246 Resolved merge conflicts in InvokeHTTP and various poms. Removed empty authorize.jsp. --- nifi-assembly/pom.xml | 4 +- .../nifi-oauth-api-nar/pom.xml | 7 +- .../nifi-oauth-bundle/nifi-oauth-api/pom.xml | 2 +- .../nifi-oauth-bundle/nifi-oauth-nar/pom.xml | 8 +- .../nifi-oauth-bundle/nifi-oauth/pom.xml | 6 +- nifi-nar-bundles/nifi-oauth-bundle/pom.xml | 4 +- .../nifi/processors/standard/InvokeHTTP.java | 84 ++++++------------- .../src/main/webapp/WEB-INF/jsp/authorize.jsp | 0 8 files changed, 43 insertions(+), 72 deletions(-) delete mode 100644 nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/authorize.jsp diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml index f5464b4d6692..88aecbd16d94 100755 --- a/nifi-assembly/pom.xml +++ b/nifi-assembly/pom.xml @@ -676,13 +676,13 @@ language governing permissions and limitations under the License. --> org.apache.nifi nifi-oauth-api-nar - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nar org.apache.nifi nifi-oauth-nar - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nar diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml index 39081f4f80c3..524442625dfc 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api-nar/pom.xml @@ -19,11 +19,11 @@ org.apache.nifi nifi-oauth-bundle - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nifi-oauth-api-nar - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nar true @@ -34,12 +34,13 @@ org.apache.nifi nifi-standard-services-api-nar + 1.6.0-SNAPSHOT nar org.apache.nifi nifi-oauth-api - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml index 1ee12db3c4ab..7c9f3fdd8c39 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-api/pom.xml @@ -19,7 +19,7 @@ org.apache.nifi nifi-oauth-bundle - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nifi-oauth-api diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml index a403191a9f74..d8a11eedb22a 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth-nar/pom.xml @@ -19,11 +19,11 @@ org.apache.nifi nifi-oauth-bundle - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nifi-oauth-nar - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nar true @@ -34,13 +34,13 @@ org.apache.nifi nifi-oauth-api-nar - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nar org.apache.nifi nifi-oauth - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml index 8fb188b69cb9..82c34f8d4946 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/pom.xml @@ -19,7 +19,7 @@ org.apache.nifi nifi-oauth-bundle - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT nifi-oauth @@ -29,7 +29,7 @@ org.apache.nifi nifi-oauth-api - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT org.apache.oltu.oauth2 @@ -44,10 +44,12 @@ org.apache.nifi nifi-processor-utils + 1.6.0-SNAPSHOT org.apache.nifi nifi-mock + 1.6.0-SNAPSHOT test diff --git a/nifi-nar-bundles/nifi-oauth-bundle/pom.xml b/nifi-nar-bundles/nifi-oauth-bundle/pom.xml index 7deaab7ca6ae..8ae9c9c0b9ce 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/pom.xml +++ b/nifi-nar-bundles/nifi-oauth-bundle/pom.xml @@ -19,12 +19,12 @@ org.apache.nifi nifi-nar-bundles - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT org.apache.nifi nifi-oauth-bundle - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT pom diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java index b615787d8b4d..0a731ae328eb 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java @@ -16,23 +16,15 @@ */ package org.apache.nifi.processors.standard; +import static org.apache.commons.lang3.StringUtils.trimToEmpty; + import com.burgstaller.okhttp.AuthenticationCacheInterceptor; import com.burgstaller.okhttp.CachingAuthenticatorDecorator; import com.burgstaller.okhttp.digest.CachingAuthenticator; import com.burgstaller.okhttp.digest.DigestAuthenticator; import com.google.common.io.Files; -import okhttp3.Cache; -import okhttp3.Credentials; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.internal.tls.OkHostnameVerifier; -import okio.BufferedSink; -import static org.apache.commons.lang3.StringUtils.trimToEmpty; - +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; @@ -41,6 +33,12 @@ import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -57,11 +55,25 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; - +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import okhttp3.Cache; +import okhttp3.Credentials; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.internal.tls.OkHostnameVerifier; +import okio.BufferedSink; import org.apache.commons.io.input.TeeInputStream; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.annotation.behavior.DynamicProperty; @@ -95,50 +107,6 @@ import org.apache.nifi.stream.io.StreamUtils; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.Proxy.Type; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.apache.commons.lang3.StringUtils.trimToEmpty; @SupportsBatching @Tags({"http", "https", "rest", "client"}) diff --git a/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/authorize.jsp b/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/authorize.jsp deleted file mode 100644 index e69de29bb2d1..000000000000 From badedabecdcac9830c4c7473347a7e0489e8c070 Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Sun, 18 Mar 2018 18:41:08 -0400 Subject: [PATCH 09/11] Require HTTPS for authentication URL, removed unused Java properties from inner static class, and other changes --- .../oauth/AbstractOAuthControllerService.java | 33 ++++++-- ...ientCredentialsGrantControllerService.java | 2 +- .../httpclient/OAuthHTTPConnectionClient.java | 80 +++++++++---------- 3 files changed, 65 insertions(+), 50 deletions(-) diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java index 5f3fb99d1ca8..22b67a002a19 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java @@ -22,8 +22,12 @@ import java.util.Map; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; import org.apache.nifi.controller.AbstractControllerService; import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.reporting.InitializationException; @@ -39,26 +43,44 @@ public abstract class AbstractOAuthControllerService protected long lastResponseTimestamp = -1; protected Map extraHeaders = new HashMap(); protected String authUrl = null; - protected long expireTimeSafetyNetSeconds = -1; protected String accessTokenRespName = null; protected String expireTimeRespName = null; protected String expireInRespName = null; protected String tokenTypeRespName = null; protected String scopeRespName = null; + public static final Validator HTTPS_VALIDATOR = new Validator() { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + final ValidationResult.Builder builder = new ValidationResult.Builder(); + builder.subject(subject).input(input); + if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) { + return builder.valid(true).explanation("Contains Expression Language").build(); + } + + if (!input.startsWith("https")) { + return builder.valid(false).explanation("OAuth2 authorization endpoint must use HTTPS").build(); + } else { + return builder.valid(true).build(); + } + } + }; + public static final PropertyDescriptor AUTH_SERVER_URL = new PropertyDescriptor .Builder().name("oauth2_authorization_server_url") .displayName("OAuth2 Authorization Server") - .description("OAuth2 Authorization Server that grants access to the protected resources on the behalf of the resource owner.") + .description("HTTPS OAuth2 Authorization Server that grants access to the protected resources on " + + "the behalf of the resource owner.") .required(true) .expressionLanguageSupported(true) - .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .addValidator(HTTPS_VALIDATOR) .build(); public static final PropertyDescriptor RESPONSE_ACCESS_TOKEN_FIELD_NAME = new PropertyDescriptor .Builder().name("JSON_response_access_token_name") .displayName("JSON response 'access_token' name") - .description("Name of the field in the JSON response that contains the access token. IETF OAuth2 spec default is 'access_token' if your API provider's" + + .description("Name of the field in the JSON response that contains the access token. " + + "IETF OAuth2 spec default is 'access_token' if your API provider's" + " response field is different this is where you can change that.") .defaultValue("access_token") .required(true) @@ -69,7 +91,8 @@ public abstract class AbstractOAuthControllerService public static final PropertyDescriptor RESPONSE_EXPIRE_TIME_FIELD_NAME = new PropertyDescriptor .Builder().name("JSON_response_access_token_name") .displayName("JSON response 'expire_time' name") - .description("Name of the field in the JSON response that contains the expire time. IETF OAuth2 spec default is 'expire_time' if your API provider's" + + .description("Name of the field in the JSON response that contains the expire time. IETF OAuth2 spec " + + "default is 'expire_time' if your API provider's" + " response field is different this is where you can change that.") .defaultValue("expire_time") .required(true) diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java index 45568a5bb27b..bd2698440ac1 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java @@ -91,7 +91,7 @@ protected List getSupportedPropertyDescriptors() { /** * @param context the configuration context - * @throws InitializationException if unable to create a database connection + * @throws InitializationException */ @OnEnabled public void onEnabled(final ConfigurationContext context) throws InitializationException { diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java index cc4fd7541d7b..0053d53e5ada 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java @@ -68,44 +68,50 @@ public T execute(OAuthClientRequest request, Map Map> responseHeaders = new HashMap>(); int responseCode; try { - URL url = new URL(request.getLocationUri()); - - c = url.openConnection(); - responseCode = -1; - if (c instanceof HttpURLConnection) { - HttpURLConnection httpURLConnection = (HttpURLConnection) c; + String locURI = request.getLocationUri(); + if (locURI.startsWith("https")) { + URL url = new URL(request.getLocationUri()); + + c = url.openConnection(); + responseCode = -1; + if (c instanceof HttpURLConnection) { + HttpURLConnection httpURLConnection = (HttpURLConnection) c; + + if (headers != null && !headers.isEmpty()) { + for (Map.Entry header : headers.entrySet()) { + httpURLConnection.addRequestProperty(header.getKey(), header.getValue()); + } + } - if (headers != null && !headers.isEmpty()) { - for (Map.Entry header : headers.entrySet()) { - httpURLConnection.addRequestProperty(header.getKey(), header.getValue()); + if (request.getHeaders() != null) { + for (Map.Entry header : request.getHeaders().entrySet()) { + httpURLConnection.addRequestProperty(header.getKey(), header.getValue()); + } } - } - if (request.getHeaders() != null) { - for (Map.Entry header : request.getHeaders().entrySet()) { - httpURLConnection.addRequestProperty(header.getKey(), header.getValue()); + if (OAuthUtils.isEmpty(requestMethod)) { + httpURLConnection.setRequestMethod(OAuth.HttpMethod.GET); + } else { + httpURLConnection.setRequestMethod(requestMethod); + setRequestBody(request, requestMethod, httpURLConnection); } - } - if (OAuthUtils.isEmpty(requestMethod)) { - httpURLConnection.setRequestMethod(OAuth.HttpMethod.GET); - } else { - httpURLConnection.setRequestMethod(requestMethod); - setRequestBody(request, requestMethod, httpURLConnection); - } + httpURLConnection.connect(); - httpURLConnection.connect(); + InputStream inputStream; + responseCode = httpURLConnection.getResponseCode(); + if (responseCode == 400 || responseCode == 405 || responseCode == 401 || responseCode == 403) { + inputStream = httpURLConnection.getErrorStream(); + } else { + inputStream = httpURLConnection.getInputStream(); + } - InputStream inputStream; - responseCode = httpURLConnection.getResponseCode(); - if (responseCode == 400 || responseCode == 405 || responseCode == 401 || responseCode == 403) { - inputStream = httpURLConnection.getErrorStream(); - } else { - inputStream = httpURLConnection.getInputStream(); + responseHeaders = httpURLConnection.getHeaderFields(); + responseBody = inputStream; } - - responseHeaders = httpURLConnection.getHeaderFields(); - responseBody = inputStream; + } else { + throw new OAuthSystemException("OAuth authentication endpoint " + request.getLocationUri() + " is not " + + "HTTPS. HTTPS is required for an OAuth authentication endpoint to be valid"); } } catch (IOException e) { throw new OAuthSystemException(e); @@ -143,13 +149,6 @@ public void shutdown() { public static class CustomOAuthAccessTokenResponse extends OAuthAccessTokenResponse { - // Names of the fields that should be pulled from the JSON response. - private String accessTokenName = null; - private String tokenTypeName = null; - private String scopeName = null; - private String expireInName = null; - private String expireTimeName = null; - private String accessToken; private int responseCode; private String tokenType; @@ -161,13 +160,6 @@ public CustomOAuthAccessTokenResponse(InputStream responseBody, String contentTy Map> responseHeaders, String accessTokenName, String tokenTypeName, String scopeName, String expireInName, String expireTimeName) throws OAuthProblemException { - // Set the field names - this.accessTokenName = accessTokenName; - this.tokenTypeName = tokenTypeName; - this.scopeName = scopeName; - this.expireInName = expireInName; - this.expireTimeName = expireTimeName; - setResponseCode(responseCode); this.responseCode = responseCode; setContentType(contentType); From 3aa32a7dc7d0fd1a210b58f84d7fa75f9cfa7e8a Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Sun, 18 Mar 2018 18:58:47 -0400 Subject: [PATCH 10/11] Support expression language for CLIENT_ID and CLIENT_SECRET --- .../oauth/OAuth2ClientCredentialsGrantControllerService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java index bd2698440ac1..71c02a023330 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java @@ -53,6 +53,7 @@ public class OAuth2ClientCredentialsGrantControllerService .displayName("OAuth2 Client ID") .description("OAuth2 Client ID passed to the authorization server") .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); @@ -62,6 +63,7 @@ public class OAuth2ClientCredentialsGrantControllerService .description("OAuth2 Client Secret that will be passed to the authorization server in exchange for an access token") .sensitive(true) .required(true) + .expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); From ed2983e4b8fcfd6f9eb989f52696d078bfd7c88d Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Mon, 19 Mar 2018 08:59:48 -0400 Subject: [PATCH 11/11] build enhancements --- .../oauth/AbstractOAuthControllerService.java | 1 - ...ientCredentialsGrantControllerService.java | 47 ++++++++++--------- .../httpclient/OAuthHTTPConnectionClient.java | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java index 22b67a002a19..d60dffb52d16 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/AbstractOAuthControllerService.java @@ -27,7 +27,6 @@ import org.apache.nifi.components.Validator; import org.apache.nifi.controller.AbstractControllerService; import org.apache.nifi.controller.ConfigurationContext; -import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.reporting.InitializationException; diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java index 71c02a023330..c92967d28904 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java @@ -1,20 +1,18 @@ -/** +/* * 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 - *

+ * + * 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. - *

- * Created on 7/25/17. */ package org.apache.nifi.oauth; @@ -36,8 +34,6 @@ import org.apache.nifi.reporting.InitializationException; import org.apache.oltu.oauth2.client.HttpClient; import org.apache.oltu.oauth2.client.request.OAuthClientRequest; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.types.GrantType; @Tags({ "oauth2", "client", "secret", "post"}) @@ -87,13 +83,14 @@ protected List getSupportedPropertyDescriptors() { return properties; } + private HttpClient con = null; private String clientId = null; private String clientSecret = null; private Base64.Encoder enc = Base64.getEncoder(); /** * @param context the configuration context - * @throws InitializationException + * @throws InitializationException error which occurred while trying to setup the client properties. */ @OnEnabled public void onEnabled(final ConfigurationContext context) throws InitializationException { @@ -105,7 +102,6 @@ public void onEnabled(final ConfigurationContext context) throws InitializationE } public boolean authenticate() { - HttpClient con = null; try { @@ -136,22 +132,19 @@ public boolean authenticate() { con = new OAuthHTTPConnectionClient(accessTokenRespName, tokenTypeRespName, scopeRespName, expireInRespName, expireTimeRespName); OAuthHTTPConnectionClient.CustomOAuthAccessTokenResponse authResp = con.execute(headerRequest, null, "POST", OAuthHTTPConnectionClient.CustomOAuthAccessTokenResponse.class); - this.accessToken = authResp.getAccessToken(); - this.expiresIn = authResp.getExpiresIn(); - this.refreshToken = authResp.getRefreshToken(); - this.tokenType = authResp.getTokenType(); - this.lastResponseTimestamp = System.currentTimeMillis(); + if (authResp != null) { + this.accessToken = authResp.getAccessToken(); + this.expiresIn = authResp.getExpiresIn(); + this.refreshToken = authResp.getRefreshToken(); + this.tokenType = authResp.getTokenType(); + this.lastResponseTimestamp = System.currentTimeMillis(); - if (getLogger().isDebugEnabled()) { - getLogger().debug("Local variables set and OAuth2 InvokeHTTP processor is now ready for operation with Access Token"); + if (getLogger().isDebugEnabled()) { + getLogger().debug("Local variables set and OAuth2 InvokeHTTP processor is now ready for operation with Access Token"); + } + return true; } - return true; - - } catch (OAuthProblemException ope) { - getLogger().error(ope.getMessage()); - } catch (OAuthSystemException ose) { - getLogger().error(ose.getMessage()); } catch (Exception e) { getLogger().error(e.getMessage()); } finally { @@ -164,6 +157,14 @@ public boolean authenticate() { @OnDisabled public void shutdown() { + // nothing to do here + } + + public HttpClient getCon() { + return con; + } + public void setCon(HttpClient con) { + this.con = con; } } \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java index 0053d53e5ada..d14ac7a72b66 100644 --- a/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java +++ b/nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/httpclient/OAuthHTTPConnectionClient.java @@ -69,7 +69,7 @@ public T execute(OAuthClientRequest request, Map int responseCode; try { String locURI = request.getLocationUri(); - if (locURI.startsWith("https")) { + if (locURI != null && locURI.startsWith("https")) { URL url = new URL(request.getLocationUri()); c = url.openConnection();