You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@nifi.apache.org by jdye64 <gi...@git.apache.org> on 2017/08/15 00:42:16 UTC

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

GitHub user jdye64 opened a pull request:

    https://github.com/apache/nifi/pull/2085

    NIFI-4246 - Client Credentials Grant based OAuth2 Controller Service

    Thank you for submitting a contribution to Apache NiFi.
    
    In order to streamline the review of the contribution we ask you
    to ensure the following steps have been taken:
    
    ### For all changes:
    - [X] Is there a JIRA ticket associated with this PR? Is it referenced 
         in the commit message?
    
    - [X] Does your PR title start with NIFI-XXXX where XXXX is the JIRA number you are trying to resolve? Pay particular attention to the hyphen "-" character.
    
    - [X] Has your PR been rebased against the latest commit within the target branch (typically master)?
    
    - [X] Is your initial contribution a single, squashed commit?
    
    ### For code changes:
    - [X] Have you ensured that the full suite of tests is executed via mvn -Pcontrib-check clean install at the root nifi folder?
    - [ ] Have you written or updated unit tests to verify your changes?
    - [X] If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under [ASF 2.0](http://www.apache.org/legal/resolved.html#category-a)? 
    - [ ] If applicable, have you updated the LICENSE file, including the main LICENSE file under nifi-assembly?
    - [X] If applicable, have you updated the NOTICE file, including the main NOTICE file found under nifi-assembly?
    - [X] If adding new Properties, have you added .displayName in addition to .name (programmatic access) for each of the new properties?
    
    ### For documentation related changes:
    - [ ] Have you ensured that format looks appropriate for the output in which it is rendered?
    
    ### Note:
    Please ensure that once the PR is submitted, you check travis-ci for build issues and submit an update to your PR as soon as possible.


You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/jdye64/nifi NIFI-4246

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/nifi/pull/2085.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #2085
    
----
commit 6a63e544168f2db0059bab9b155ef9e3bd034531
Author: Jeremy Dyer <je...@apache.org>
Date:   2017-07-25T14:07:24Z

    NIFI-4246 - Client Credentials Grant based OAuth2 Controller Service

----


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] nifi issue #2085: NIFI-4246 - Client Credentials Grant based OAuth2 Controll...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on the issue:

    https://github.com/apache/nifi/pull/2085
  
    There are merge conflicts in `InvokeHTTP` and in a number of the poms. I believe I have resolved all of these [6a80063313f9c8e03a1f42ae84813cabd5fc8349](https://github.com/alopresto/nifi/commit/6a80063313f9c8e03a1f42ae84813cabd5fc8349). There are also the following issues:
    
    * It looks like somehow an empty file was added at `nifi/nifi-nar-bundles/nifi-update-attribute-bundle/nifi-update-attribute-ui/src/main/webapp/WEB-INF/jsp/authorize.jsp`. I only noticed this because it failed the RAT check. I imagine this can be deleted?
    * An unused field remains in `AbstractOAuthControllerService`: `protected long expireTimeSafetyNetSeconds = -1;`
    * `OAuth2ClientCredentialsGrantControllerService.CLIENT_ID` and `OAuth2ClientCredentialsGrantControllerService.CLIENT_SECRET` don't support expression language (with the variable registry and NiFi Registry, these values will likely be populated externally)
    * The exception handling in `OAuth2ClientCredentialsGrantControllerService#authenticate` can be condensed
    * There are no unit or integration tests. I understand a real integration test with an external service is difficult, but unit tests for `OAuth2ClientCredentialsGrantControllerService#authenticate()` (via mocking `OAuthHTTPConnectionClient#execute()`) and `AbstractOAuthControllerService#isOAuthTokenExpired()` would be helpful
    * There is no update to the User Guide or Admin Guide describing this feature. At a minimum, the `InvokeHTTP` processor documentation should be updated
    
    I am in the process of configuring an external OAuth service to use with this. Will have a final review once that evaluation is complete. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174886395
  
    --- Diff: 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 extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
    +            String requestMethod, Class<T> responseClass) throws OAuthSystemException, OAuthProblemException {
    +
    +        InputStream responseBody = null;
    +        URLConnection c;
    +        Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>();
    +        int responseCode;
    +        try {
    +            URL url = new URL(request.getLocationUri());
    --- End diff --
    
    This method needs to be improved to make a successful connection. Given the two *auth server* values of `apachenifi.auth0.com` and `https://apachenifi.auth0.com`, I got the following respective errors:
    
    * `2018-03-15 11:24:03,761 ERROR [Timer-Driven Process Thread-4] o.a.n.o.OAuth2ClientCredentialsGrantControllerService OAuth2ClientCredentialsGrantControllerService[id=01621005-6aa2-173b-d160-59d53622ab46] java.net.MalformedURLException: no protocol: apachenifi.auth0.com`
    * `2018-03-15 11:25:49,002 ERROR [Timer-Driven Process Thread-3] o.a.n.o.OAuth2ClientCredentialsGrantControllerService OAuth2ClientCredentialsGrantControllerService[id=01621005-6aa2-173b-d160-59d53622ab46] java.io.FileNotFoundException: https://apachenifi.auth0.com`


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r168661430
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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()) {
    --- End diff --
    
    Jeremy, sorry, I don't think I'm following. I can understand the desire to prevent token expiration, but I don't understand how that maps directly here or how the current code handles the scenario I described above. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r160197726
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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()) {
    --- End diff --
    
    Andy. I think I could have chosen a better name for the "expireTimeSafetyNetSeconds" variable. My intended behavior was for that variable to be more of a method for causing the auth token to be refresh before the token was actually expired. My thought process around this was if users could control having their token refreshed before they were actually invalid it would allow them a rough way to help prevent bottlenecking of processing when the token was invalid and the processors using the controller service were having to wait on the token to be refreshed. Does that make sense?


---

[GitHub] nifi issue #2085: NIFI-4246 - Client Credentials Grant based OAuth2 Controll...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on the issue:

    https://github.com/apache/nifi/pull/2085
  
    Hi @jdye64 -- do you have a specific service you used for testing this?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r172294029
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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()) {
    --- End diff --
    
    Jeremy and I discussed and since the fail state is that the CS just tries to reauthenticate, this variable and logic will be removed completely. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r160197767
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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));
    --- End diff --
    
    Same as above


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155117627
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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")
    --- End diff --
    
    The `name` should avoid special characters and spaces for serialization purposes. Suggest `.name("JSON_response_token_type_name")`. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r172294695
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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" +
    --- End diff --
    
    Your right this needs to be changed to true


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155117765
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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")
    --- End diff --
    
    The `name` should avoid special characters and spaces for serialization purposes. Suggest `.name("expire_time_safety_net")` (intentionally do not include units in name to allow for future evolution). 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174658141
  
    --- Diff: nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java ---
    @@ -0,0 +1,167 @@
    +/**
    + * 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
    + * <p>
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * <p>
    + * 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.
    + * <p>
    + * 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<PropertyDescriptor> properties;
    +
    +    static {
    +        final List<PropertyDescriptor> 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);
    +        properties = Collections.unmodifiableList(props);
    +    }
    +
    +    @Override
    +    protected List<PropertyDescriptor> 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());
    --- End diff --
    
    If the authorization server is requested over HTTP (as in the case of the example service http://term.ie), both the request parameters and the headers are transmitted in plaintext. We should either a) prevent OAuth from being used with HTTP servers or at least b) warn visibly when the secret will be transmitted in plaintext. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155123258
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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" +
    --- End diff --
    
    Is this part of the spec? I would fail safe here and assume that a token without a valid timestamp is *not* valid. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174967129
  
    --- Diff: 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 extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
    +            String requestMethod, Class<T> responseClass) throws OAuthSystemException, OAuthProblemException {
    +
    +        InputStream responseBody = null;
    +        URLConnection c;
    +        Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>();
    +        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<String, String> header : headers.entrySet()) {
    +                        httpURLConnection.addRequestProperty(header.getKey(), header.getValue());
    +                    }
    +                }
    +
    +                if (request.getHeaders() != null) {
    +                    for (Map.Entry<String, String> 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;
    --- End diff --
    
    I get your reasoning, I'm saying that these specific fields in this class aren't used. In the constructor, you set the value of each of these first five fields to the incoming parameter on lines 165 - 169, but then the values of the fields are never accessed (they are `private`, there are no getters, and they are not read within this class). Same for `responseCode` and `expiresIn` (in fact, you call `setResponseCode()` and then explicitly assign `this.responseCode` in consecutive lines?). 


---

[GitHub] nifi issue #2085: NIFI-4246 - Client Credentials Grant based OAuth2 Controll...

Posted by craigappl <gi...@git.apache.org>.
Github user craigappl commented on the issue:

    https://github.com/apache/nifi/pull/2085
  
    @jdye64 and @alopresto Can you share a status on this PR? We have a requirement to support HTTP exchanges with Oauth for a client. Has the team found a more appropriate solution elsewhere?


---

[GitHub] nifi issue #2085: NIFI-4246 - Client Credentials Grant based OAuth2 Controll...

Posted by joewitt <gi...@git.apache.org>.
Github user joewitt commented on the issue:

    https://github.com/apache/nifi/pull/2085
  
    could you rebase please @jdye64 



---

[GitHub] nifi issue #2085: NIFI-4246 - Client Credentials Grant based OAuth2 Controll...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on the issue:

    https://github.com/apache/nifi/pull/2085
  
    @alopresto Is there anything I can do to help move this along?


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r172295045
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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")
    --- End diff --
    
    Good point I will change the name and enable expression language by default


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155127820
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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()) {
    --- End diff --
    
    Let's say `System.currentTimeMillis()` returns a static value *1000* representing "now" and `expiresTime` is set by the remote system and returns a static value *3000* (2 seconds after *now*). With the default value of `expireTimeSafetyNetSeconds` as *10*, if the clock jitter is non-existent or "positive" (i.e. the local system's clock is "ahead"/returns a higher value than the remote system), this will evaluate to `3000 - (10 * 1000) <= 1000` which is `-7000 <= 1000` and eventually `true`. However, if the clock jitter is "behind", for example "now" on the remote system is *9001* while it's only *1000* locally (8.001 seconds difference, which should be within the "safety net" range), the expression will be `11001 - (10 * 1000) <= 1000` which is `1001 <= 1000` which is false. I believe this needs to use absolute value evaluations to handle both positive and negative offsets. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155117589
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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")
    --- End diff --
    
    The `name` should avoid special characters and spaces for serialization purposes. Suggest `.name("JSON_response_expire_in_name")`. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155128028
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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));
    --- End diff --
    
    I think this logic also needs to be evaluated for bidirectional time jitter as discussed above. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174967440
  
    --- Diff: nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java ---
    @@ -0,0 +1,167 @@
    +/**
    + * 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
    + * <p>
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * <p>
    + * 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.
    + * <p>
    + * 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<PropertyDescriptor> properties;
    +
    +    static {
    +        final List<PropertyDescriptor> 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);
    +        properties = Collections.unmodifiableList(props);
    +    }
    +
    +    @Override
    +    protected List<PropertyDescriptor> 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());
    --- End diff --
    
    I say we enforce HTTPS, and if someone wants to connect to an HTTP auth server, we do not let them. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155118120
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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;
    +
    --- End diff --
    
    None of the property descriptors support Expression Language. Is this an intentional decision, and if so, why? With the Variable Registry and the introduction of the Registry project, support for EL is important across the board unless there is a valid reason not to include it. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155117929
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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")
    --- End diff --
    
    The `name` should avoid special characters and spaces for serialization purposes. Suggest `.name("oauth2_authorization_server_url")`. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155117508
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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")
    --- End diff --
    
    The `name` should avoid special characters and spaces for serialization purposes. Suggest `.name("JSON_response_access_token_name")`. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155117665
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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")
    --- End diff --
    
    The `name` should avoid special characters and spaces for serialization purposes. Suggest `.name("JSON_response_scope_name")`. 


---

[GitHub] nifi issue #2085: NIFI-4246 - Client Credentials Grant based OAuth2 Controll...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on the issue:

    https://github.com/apache/nifi/pull/2085
  
    @joewitt rebased and incremented bundle version from 1.4.0-SNAPSHOT to 1.5.0-SNAPSHOT


---

[GitHub] nifi issue #2085: NIFI-4246 - Client Credentials Grant based OAuth2 Controll...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on the issue:

    https://github.com/apache/nifi/pull/2085
  
    I believe Jeremy's focused on another task right now but when he is available, I will work with him to resolve the code issues and we will verify the behavior with the relevant auth servers. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r175098169
  
    --- Diff: 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 extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
    +            String requestMethod, Class<T> responseClass) throws OAuthSystemException, OAuthProblemException {
    +
    +        InputStream responseBody = null;
    +        URLConnection c;
    +        Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>();
    +        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<String, String> header : headers.entrySet()) {
    +                        httpURLConnection.addRequestProperty(header.getKey(), header.getValue());
    +                    }
    +                }
    +
    +                if (request.getHeaders() != null) {
    +                    for (Map.Entry<String, String> 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;
    --- End diff --
    
    ahhh ... this is a great reason why you shouldn't try to look at this stuff while running through the airport because you miss stuff like this =)


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174656145
  
    --- Diff: nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java ---
    @@ -0,0 +1,167 @@
    +/**
    + * 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
    + * <p>
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * <p>
    + * 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.
    + * <p>
    + * 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<PropertyDescriptor> properties;
    +
    +    static {
    +        final List<PropertyDescriptor> 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);
    +        properties = Collections.unmodifiableList(props);
    +    }
    +
    +    @Override
    +    protected List<PropertyDescriptor> 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());
    --- End diff --
    
    Why create the Base64 encoded header as well as put the client ID and client secret in the request querystring? According to [OAuth.com :: Client Credentials](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/), the OAuth server will accept one or the other. Is there a way to know in advance which method is supported, and if both are supported, only one is necessary, correct?


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r175308002
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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;
    +
    --- End diff --
    
    That makes sense Andy. All properties now support expression language


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174933983
  
    --- Diff: 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 extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
    +            String requestMethod, Class<T> responseClass) throws OAuthSystemException, OAuthProblemException {
    +
    +        InputStream responseBody = null;
    +        URLConnection c;
    +        Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>();
    +        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<String, String> header : headers.entrySet()) {
    +                        httpURLConnection.addRequestProperty(header.getKey(), header.getValue());
    +                    }
    +                }
    +
    +                if (request.getHeaders() != null) {
    +                    for (Map.Entry<String, String> 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;
    --- End diff --
    
    These fields are used. They are used to determine which key to use when retrieving the OAuth values from the resulting OAuth JSON response. In other words the OAuth2 spec does not require that "access_token" be that exact key for example. It could be something like "AccessToken" This allows for the users to configure what key should be used when pulling from that JSON response. These fields are populated by the user by filling in PropertyDescriptors in AbstractOAuthControllerService.java


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174931088
  
    --- Diff: nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java ---
    @@ -0,0 +1,167 @@
    +/**
    + * 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
    + * <p>
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * <p>
    + * 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.
    + * <p>
    + * 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<PropertyDescriptor> properties;
    +
    +    static {
    +        final List<PropertyDescriptor> 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);
    +        properties = Collections.unmodifiableList(props);
    +    }
    +
    +    @Override
    +    protected List<PropertyDescriptor> 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());
    --- End diff --
    
    At first I thought we would just force HTTPS in the connection string. After reading that paragraph from the OAuth2 documentation it almost seems to me like just a warning is what we should do? What do you think?


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174881237
  
    --- Diff: 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 extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
    +            String requestMethod, Class<T> responseClass) throws OAuthSystemException, OAuthProblemException {
    +
    +        InputStream responseBody = null;
    +        URLConnection c;
    +        Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>();
    +        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<String, String> header : headers.entrySet()) {
    +                        httpURLConnection.addRequestProperty(header.getKey(), header.getValue());
    +                    }
    +                }
    +
    +                if (request.getHeaders() != null) {
    +                    for (Map.Entry<String, String> 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;
    --- End diff --
    
    It appears the following fields are unused in this class: `accessTokenName, tokenTypeName, scopeName, expireInName, expireTimeName, responseCode, expireTime`. 


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r175308013
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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<PropertyDescriptor, String> allProperties = context.getProperties();
    +        Iterator<PropertyDescriptor> 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" +
    --- End diff --
    
    This now returns true


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174933433
  
    --- Diff: 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 extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
    +            String requestMethod, Class<T> responseClass) throws OAuthSystemException, OAuthProblemException {
    +
    +        InputStream responseBody = null;
    +        URLConnection c;
    +        Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>();
    +        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<String, String> header : headers.entrySet()) {
    +                        httpURLConnection.addRequestProperty(header.getKey(), header.getValue());
    +                    }
    +                }
    +
    +                if (request.getHeaders() != null) {
    +                    for (Map.Entry<String, String> 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;
    --- End diff --
    
    These fields are used. They are used to determine which key to use when retrieving the OAuth values from the resulting OAuth JSON response. In other words the OAuth2 spec does not require that "access_token" be that exact key for example. It could be something like "AccessToken" This allows for the users to configure what key should be used when pulling from that JSON response. These fields are populated by the user by filling in PropertyDescriptors in AbstractOAuthControllerService.java


---

[GitHub] nifi issue #2085: NIFI-4246 - Client Credentials Grant based OAuth2 Controll...

Posted by jdye64 <gi...@git.apache.org>.
Github user jdye64 commented on the issue:

    https://github.com/apache/nifi/pull/2085
  
    @alopresto I tested it against tubemogl [1] and freewheel [2]. I obviously can't share those sensitive credentials here but would something like http://term.ie/oauth/example/ suffice? 
    
    [1] - https://www.tubemogul.com/
    [2] - http://freewheel.tv/ 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r174658418
  
    --- Diff: nifi-nar-bundles/nifi-oauth-bundle/nifi-oauth/src/main/java/org/apache/nifi/oauth/OAuth2ClientCredentialsGrantControllerService.java ---
    @@ -0,0 +1,167 @@
    +/**
    + * 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
    + * <p>
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * <p>
    + * 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.
    + * <p>
    + * 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<PropertyDescriptor> properties;
    +
    +    static {
    +        final List<PropertyDescriptor> 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);
    +        properties = Collections.unmodifiableList(props);
    +    }
    +
    +    @Override
    +    protected List<PropertyDescriptor> 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());
    --- End diff --
    
    From [Understanding OAuth2](http://www.bubblecode.net/en/2016/01/22/understanding-oauth2/):
    
    > HTTPS
    > OAuth2 requires the use of HTTPS for communication between the client and the authorization server because of sensitive data passing between the two (tokens and possibly resource owner credentials). In fact you are not forced to do so if you implement your own authorization server but you must know that you are opening a big security hole by doing this.


---

[GitHub] nifi pull request #2085: NIFI-4246 - Client Credentials Grant based OAuth2 C...

Posted by alopresto <gi...@git.apache.org>.
Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2085#discussion_r155117567
  
    --- Diff: 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<String, String> extraHeaders = new HashMap<String, String>();
    +    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")
    --- End diff --
    
    The `name` should avoid special characters and spaces for serialization purposes. Suggest `.name("JSON_response_expire_time_name")`. 


---