Skip to content

Conversation

cbornet
Copy link
Contributor

@cbornet cbornet commented Aug 29, 2015

Added basic auth, api key and oauth support to the service generator using okhttp interceptors.
For oauth2, the apache oltu lib is used.

Fix #962

added basic auth, api key and oauth support to the service generator using okhttp interceptors

Fix swagger-api#962
removed gson version in pom and added a space between the parameters
@cbornet cbornet changed the title add authorizations to retrofit client [Retrofit] add authorizations to retrofit client Sep 1, 2015
@xhh
Copy link
Contributor

xhh commented Sep 9, 2015

Hi @cbornet, I tried the petstore sample and got this error:

retrofit.RetrofitError: unexpected url:
        at retrofit.RetrofitError.unexpectedError(RetrofitError.java:44)
        at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:400)
        at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
        at com.sun.proxy.$Proxy23.findPetsByStatus(Unknown Source)
        at io.Test.main(Test.java:14)
        ... 6 more
Caused by: java.lang.IllegalArgumentException: unexpected url:
        at com.squareup.okhttp.Request$Builder.url(Request.java:157)
        at io.swagger.client.auth.OauthOkHttpClient.execute(OauthOkHttpClient.java:38)
        at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:65)
        at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:55)
        at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:71)
        at io.swagger.client.auth.OauthAuthorization.updateAccessToken(OauthAuthorization.java:114)
        at io.swagger.client.auth.OauthAuthorization.intercept(OauthAuthorization.java:78)
        at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:227)
        at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:201)
        at com.squareup.okhttp.Call.execute(Call.java:81)
        at retrofit.client.OkClient.execute(OkClient.java:53)
        at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:326)
        ... 9 more

I used this code to test:

ServiceGenerator gen = new ServiceGenerator("petstore_auth", "test_client_id", "test_client_secret", "test_username", "test_password");

PetApi api = gen.createService(PetApi.class);
List<Pet> pets = api.findPetsByStatus(Arrays.asList("available"));
System.out.println(pets.size());

My guess is that the empty tokenUrl was used to request an access token, but it should not be used in implicit flow, right?
(code pasted from here)

if (authName == "petstore_auth") { 
    auth = new OauthAuthorization(OauthFlow.implicit, "http://petstore.swagger.io/api/oauth/dialog", "", "write:pets, read:pets");
}

Could you explain how to use each auth with the generated retrofit client? For example, for HTTP basic auth, API key auth, and each of the 4 flows of OAuth2.

@cbornet
Copy link
Contributor Author

cbornet commented Sep 9, 2015

@xhh There is indeed no tokenURL when using implicit flow. So the access token should be set prior to calling the API. You can do this by calling gen.setAccessToken(token);.
There should probably be no attempt to renew the token if the flow is implicit so I will add a check on this.
Note that implicit and authorization_code flows involve a step with the 3rd party web site. The steps to use are described in https://cwiki.apache.org/confluence/display/OLTU/OAuth+2.0+Client+Quickstart. You first need to redirect the user to the 3rd party authorization URL using

    String url = gen.getAuthorizationEndPoint()
    .setClientId("your-application-client-id")
    .setRedirectURI("http://www.your-web-site.com/redirect")
    .buildQueryMessage()
    .getLocationUri();

Then open/redirect the navigator (prefered) or a open a webview (discouraged since it breaks the point of 3-legged auth) to this URL. When the user has entered its credential, the 3rd party server will redirect to your http://www.your-web-site.com/redirect with the code or accessToken as parameter which you can use to configure the ServiceGenerator with gen.setAccessToken(token) for implicit flow and gen.getTokenEndPoint().setCode(code) for authorization code.

As these flows need the use of components external to Java/retrofit such as webviews or a web server, they cannot really be included in a generic client lib.

To test the other flows you can do :

//Api key
ServiceGenerator gen = new ServiceGenerator("api_key", "my_api_key");
//Basic auth
ServiceGenerator gen = new ServiceGenerator("basic", "my_login", "my_password");
//Password oauth
ServiceGenerator gen = new ServiceGenerator("password_oauth",  "my_client_id", "my_client_secret", "my_login", "my_password");
//Application oauth
ServiceGenerator gen = new ServiceGenerator("application_oauth");
gen.getTokenEndPoint().setClientId("my_client_id").setClientSecret("my_client_secret");

I agree that a generated README would be useful. I will add one when I have time.

@xhh
Copy link
Contributor

xhh commented Sep 10, 2015

@cbornet Thanks for the explanation!
I have another two questions:

  1. for a spec that has more than one auth/security defined (e.g. the petstore sample which has api key auth and oauth), if configuring all of the authentications in one ServiceGenerator, will all of them be applied when requesting an endpoint that only needs one of the authentications? (e.g. the getInventory method only requires api key auth).
    Is it needed to configure and use different ServiceGenerator for endpoints using different authentications?
  2. for a case of one oauth2 authentication with different scopes, i.e. two different endpoints requires one same oauth authentication but different oauth scopes, then the two endpoints requires different access tokens, right? How to handle this case here?

@cbornet
Copy link
Contributor Author

cbornet commented Sep 10, 2015

@xhh

  1. Yes and yes
    There is a logical "AND" between the apiAuthorizations set in a given ServiceGenerator. For a given operation you need to use the ServiceGenerator corresponding to the "security" configuration you want to use. Note that there is a logical "OR" between the "security requirement" objects of the "security" list. For instance in getPetById you can use ServiceGenerator("api_key") OR ServiceGenerator("petstore_auth") depending on how you want to authenticate. You would need a ServiceGenerator with AND logic if the security definition was :
"security": [
          {
            "api_key": [],
            "petstore_auth": [
              "write:pets",
              "read:pets"
            ]
          }
        ]

Since it is the application role to chose which authorization to use, it is not enforced automatically onto the operation (it could be if the operation has only one way to authenticate such as getInventory but I don't see how to do that properly with the generator).
2. You need a different ServiceGenerator per scope list (eg. one for "write:pets", one for "read:pets", one for "write:pets read:pets", ...). The default ServiceGenerator is configured with all the scopes of the security definition but this can be changed with gen.getTokenEndPoint().setScope("first_scope second_scope third_scope")

@wing328
Copy link
Contributor

wing328 commented Sep 15, 2015

If no one has further question, I'll merge this PR tomorrow (Wed)

wing328 added a commit that referenced this pull request Sep 17, 2015
[Retrofit] add authorizations to retrofit client
@wing328 wing328 merged commit b3ca9cd into swagger-api:master Sep 17, 2015
@cbornet cbornet deleted the retrofit_oauth branch September 17, 2015 11:32
@fehguy fehguy modified the milestone: v2.1.4 Oct 26, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants