Coder Social home page Coder Social logo

andretietz / retroauth Goto Github PK

View Code? Open in Web Editor NEW
405.0 13.0 38.0 1.11 MB

A library build on top of retrofit, for simple handling of authenticated requests

License: Apache License 2.0

Kotlin 100.00%
android java retrofit2 authentication token-authentication token-refresh annotation oauth2-client oauth2 oauth

retroauth's People

Contributors

amit-newput avatar andretietz avatar jgerdes avatar mauin avatar monowar1993 avatar vanniktech avatar xinyuez avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

retroauth's Issues

What am I supposed to do with AuthenticationRequiredException, and other related questions

Hi,

I'm a bit confused about the new AuthenticationRequiredException you introduced in #68 . Why is this exception thrown? After this exception the request has failed, how do I know when to retry the request?

Also, when I make a call in Activity.onCreate the login activity doesn't open because the ActivityManager.activity is null, probably because of this line. Is there a reason you don't push the activity on onActivityCreated?

When the login activity does open (when clicking a refresh button for example) and if I then cancel the login by going back, I don't seem to get a AuthenticationCanceledException, nothing seems to happen. Is this because the opening of the login activity is now separate from the actual authenticated call being made?

ANDROID OperationCanceledException on authenticated call

I'm trying to use your utility with my own OAuth server and API.

I had followed your tutorial but when I try to call asynchronously an authenticated method it throws an AuthenticationCanceledException which contains an OperationCanceledException triggered by android.accounts.AccountManager$AmsTask.internalGetResult(AccountManager.java:1503) in com.andretietz.retroauth.AndroidTokenStorage.getToken(AndroidTokenStorage.java:87).

My service is defined as :

@Authenticated({R.string.authentication_ACCOUNT, R.string.authentication_TOKEN})
@GET("me")
Call<CurrentPerson> getMe();

And it is called in an activity onCreate :

// SnPAPIHandler::getApiService() returns the service defined above
Call<CurrentPerson> call = SnpAPIHandler.getInstance().getApiService().getMe();
call.enqueue(new Callback<CurrentPerson>() {
    @Override
    public void onResponse(Call<CurrentPerson> call, Response<CurrentPerson> response) {
        if (response.isSuccessful()) {
            // Populate activity view
        } else {
            // Display an error message
        }
    }

    @Override
    public void onFailure(Call<CurrentPerson> call, Throwable t) {
        // The exception is catched here!
        t.printStackTrace();
        Log.e("AUTH ERR", "Error", t);
        // Display an error message
    }
});

What am I missing?

Enable Locking doesn't seem to be working

As you mentioned enableLocking in #43 , my understanding is that if I set this to true, I can fire multiple requests at (more or less) the same time, and they will wait for each other to authenticate.

So that's what I'm doing. I set it to true, on app startup I do several calls, and all calls use the same account type and token. However the login activity is opened for each call.

When I'm debugging I can see that there is a lock for each call in the CredentialInterceptor.TOKENTYPE_LOCKERS Hashmap. Is that supposed to be the case? All my calls are annotated exactly the same way:
@Authenticated({R.string.account_type, R.string.authentication_token})
(by the way, is there a way to set a default @Authenticated for all calls?)

Is this a bug or am I doing something wrong? Or am I misunderstanding enableLocking?

Tests

The main functionality of the requests should be tested with either unit- or instrumentation tests

[Question] about RetroauthCallAdapterFactory

i had a question about RetroauthCallAdapterFactory specially about its first parameter
private val callAdapterFactories: List<CallAdapter.Factory>.

why we are passing the factories as parameter while we can get it from retrofit instance passed to get method. this way we don't have to do Retrofit2Platform workaround to get the defaultCallAdapterFactories

i am just curious about this approach if you can tell me more about it that would be great, thanks

Check if the subscriber unsubscribed before executing a request

Usually, when you call the request method itself in the code, the request will be executed immediately. But in case of a request is waiting for anotherone (regarding to the RequestStrategy) it could happen, that a request is not supposed to be executed anymore. right now it would be executed anyway (this could happen only if a login, which might appear, was not canceled).

Retrofit 2.5.0 crash

Hey,

If I change Retrofit version to 2.5.0 my app crashes with this stacktrace because of Retroauth:

java.lang.NoSuchMethodError: No virtual method defaultCallAdapterFactory(Ljava/util/concurrent/Executor;)Lretrofit2/CallAdapter$Factory; in class Lretrofit2/Platform; or its super classes (declaration of 'retrofit2.Platform' appears in /data/app/app.debug-od8qquOpDpUaSqITAUoBgw==/split_lib_dependencies_apk.apk:classes2.dex)
        at retrofit2.Retrofit2Platform.defaultCallAdapterFactory(Retrofit2Platform.kt:31)
        at com.andretietz.retroauth.Retroauth$Builder.build(Retroauth.kt:115)

Retroauth uses Platform#defaultCallAdapterFactory(executor) but in 2.5.0 it's renamed to Platform#defaultCallAdapterFactories(executor).

[Info] retrofit2 supports @RequestInterceptor for each method

retrofit2 is a just extension for square/retrofit. It's just a extension for retrofit now, and I'm testing it. Your project is beginning, so I think i can get some feedbacks from you. It supports @RequestInterceptor for each method, i think it's helpful for you.

See Also:

Storing more than one Token

Right now, if you call the finalize method of the AuthenticationActivity, you are able to store one token.
What if I want to store 3 and all of them are from a different token type?
This will be part of the next release

Account chooser intent or getAccounts methods

Could be nice to have this method so developers can launch a chooseAccountIntent using your class.
Or maybe directly wrap the AccountManager.newChooseAccountIntent. Something like:

   public Intent getAccountSelectorIntent(@NonNull String accountType) {
        ArrayList<Account> accounts = new ArrayList<> (Arrays.asList(mAccountManager.getAccountsByType(accountType)));
        Account account = getActiveAccount(accountType);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return AccountManager.newChooseAccountIntent(account, accounts,
                    new String[]{getString(R.string.authentication_ACCOUNT)}, null, null,
                    null, null);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return AccountManager.newChooseAccountIntent(account, accounts,
                    new String[]{getString(R.string.authentication_ACCOUNT)}, false, null, null,
                    null, null);
        } else {
            return AccountPicker.newChooseAccountIntent(account, accounts,
                    new String[]{getString(R.string.authentication_ACCOUNT)}, false, null,
                    getString(R.string.authentication_TOKEN), null, null);
        }
    }

make finalizeAuthentication not finalize

Just a thought, why is this method finishing the activity? Maybe you can break it up, because for my case after login the user might need to follow a onboarding process, so finishing the activity makes no sense yet.

For now i've just created my own variant of the AuthenticationActivity, but it might be easier for others if finishing the activity is optional :)

Element value must be a constant expression

Hi,

In my project the line

@Authentication(accountType = R.string.auth_account_type, tokenType = R.string.auth_token_type)

does not want to compile, I get this error :
Error:(6, 39) error: element value must be a constant expression

The R.string does not seems to be seen as a constant.

Thanks !

removeActiveAccount needs callback

When calling removeActiveAccount on the AuthAccountManager, there's no callback telling you, when the account is actually deleted. Since the actual deletion is async, this is a problem in some cases.

Switching from String[] to int[] in the Authenticated-Annotation

I figured out a problem, when using retroauth-android within another android library. It's not a major issue, but it would be way easier, when instead of the String array in the Authenticated-Annotation, there would be an int array.

In an android library you can use declared (but undefined) strings using the string resource id.
For java projects it doesn't need to be a string anyways, so I started migrating.

What do you think?
@Diolor @xiaolongyuan

Since this would break your build on update, it'll be 2.1.0. A snapshot version is available already

compile 'com.andretietz.retroauth:retroauth-core:2.1.0-SNAPSHOT'

and

compile 'com.andretietz.retroauth:retroauth-android:2.1.0-SNAPSHOT'

As usual in the snapshot repository:

maven { url('https://oss.jfrog.org/artifactory/oss-snapshot-local') }

It also contains the newest dependencies (retrofit:2.1.0 and appcompat:24.0.0)

Javadocs

Each class and at least all public methods of those, should provide a helpful documentation

Requests are stuck after logging out

Hey,

I'm not sure if it's related to retroauth or not, but I have this edge case issue:

  • User opens the app (MainActivity)
  • Network request is made with @Authenticated
  • API returns an auth error, because the token is no more valid (or some other token related error)
  • App catches the error and logs out the user: removes the active owner (ownerManager.getActiveOwner(), ownerManager.removeOwner()), opens LoginActivity with clear top
  • When the app is trying to log out the user, if the user quickly clicks back or some other UI button that opens another screen or else: LoginActivity opens, but the user can click back button and open MainActivity which was supposed to be cleared from the stack (or other last clicked screen)
  • At this point, App gets to MainActivity or other screens that shows the last error auth error. If the user clicks a retry button or refreshes via swipeRefresh: App retries the network request with @Authenticated. The expected behavior is to open LoginActivity or account chooser because last active owner was removed. But it somehow gets stuck, and App doesn't receive any errors and stays in the loading state. If the user invokes another UI element that makes another network request, the last request gets canceled (rxjava's disposables.clear()) and throws swallowed android.accounts.OperationCanceledException.

When I started writing this, I wasn't sure if it was retroauth's bug, but now as I know where it gets stuck, it seems like it's retroauth's bug :). I don't know if it can be replicated or not though.

Have you had this issue before?
I'm using version 3.0.0-beta6

Thanks.

Sources for 3-beta7 ?

Hey (me again ;) )

I'm back to work on migrating to version 3, but I have an weird behavioral error (most likely related to the locking in the CredentialInterceptor) that I want to debug. But it seems like Android Studio cannot find the sources for beta7. So now I have no idea what's happening.

Are the sources included in the release? If not, could you do that? Thanks

create unauthenticated api without TokenInterceptor or context

In some cases you need a plain retrofit service (especially, when you're calling some authentication apis). Since the AuthRestAdapter (as well as the retrofit.RestAdapter) uses caching for all created apis, it would be nice to use this on AuthRestAdapter to handle all calls, including unauthenticated ones.

A workaround for now is to add an empty Imlementation of the TokenInterceptor (or any other tokeninterceptor)

I'll add an additional method to the AuthRestAdapter to create unauthenticated Services without the retroauth "magic".

Version 3, backwards compatibility of existing (refresh) tokens/accounts?

Hi,

I'm looking in to migrating to version 3 of the library, and had some questions regarding backwards compatibility of accounts and tokens stored with version 2.

  1. Does v3 work with owners/tokens stored with v2?
  2. How is the refresh token stored now? V2 AndroidToken had a refresh token parameter that seems to be gone in V3. (the Javadoc of AuthenticationActivity seems to be outdated)

Custom TokenTypeFactory<String> fails in 2.1.3

AndroidAuthenticationHandler.create(provider, new CustomTokenTypeFactory());

Wrong 2nd argument type. Found: 'com.imin.core.auth.CustomTokenTypeFactory', required: 'com.andretietz.retroauth.TokenTypeFactory<com.andretietz.retroauth.AndroidTokenType>'

Not sure why this fails, because the tests do work

Doble login activity opened

I hope i could but i was no able to locate the error.
Sometimes the login activity is opened twice when the app starts.
Here is my provider code(not so rare).

public class AccountProvider implements Provider<Account, AndroidTokenType, AndroidToken> {
    private static final String TAG = "AccountProvider";

    @Override
    public Request authenticateRequest(Request request, AndroidToken androidToken) {
        return request.newBuilder()
                .header("Authorization", "Token " + androidToken.token)
                .build();
    }

    @Override
    public boolean retryRequired(int i, Response response, final TokenStorage<Account, AndroidTokenType, AndroidToken> tokenStorage, final Account account, final AndroidTokenType androidTokenType, final AndroidToken androidToken) {
        if (!response.isSuccessful()) {
            if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
                BaseUser user = new BaseUser(account.name, androidToken.refreshToken);
                Log.d(TAG, "Retry Required: " + account.name + " " + androidToken.refreshToken);
                tokenStorage.removeToken(account, androidTokenType, androidToken);
                try {
                    retrofit2.Response<LoginWrapper> res = AccountGeneral.syncSignIn(user);
                    if(res.isSuccessful()){
                        tokenStorage.storeToken(account, androidTokenType, new AndroidToken(res.body().getToken(), androidToken.refreshToken));
                        res.body().setPassword(androidToken.refreshToken);
                        AccountGeneral.saveUser(account, res.body().getUser());
                        return true;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    response.close();
                }
            }
            Log.d(TAG, "retryRequired: " + response.code());
        }
        return false;
    }
}

My ActivityLogin(it use fragments so i handle the actions in methods onSuccess and onError)

public class ActivityLogin extends AuthenticationActivity implements ILoginInteractionListener{

    private static final String TAG = "ActivityLogin";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        getSupportFragmentManager().beginTransaction()
                .replace(R.id.login_content, new FragmentLogin(), FragmentLogin.TAG).commit();
    }

    @Override
    public void onBackPressed() {
        if (getSupportFragmentManager().findFragmentByTag(FragmentSignUp.TAG) != null) {
            getSupportFragmentManager().popBackStack();
        }else{
            finish();
        }
    }

    @Override
    public void onSuccess(LoginWrapper loginWrapper) {
        Log.d(TAG, "onSuccess: " + loginWrapper.getUser().getUsername());
        Account account = createOrGetAccount(loginWrapper.getUser().getUsername());
        storeToken(account, getString(R.string.authentication_TOKEN), loginWrapper.getToken(), loginWrapper.getUser().getPassword());
        AccountGeneral.saveUser(account, loginWrapper.getUser());
        finalizeAuthentication(account);
    }

    @Override
    public void onError() {
        Log.d(TAG, "onError");
        ILoginFragment f = (ILoginFragment) ((getSupportFragmentManager().findFragmentByTag(FragmentLogin.TAG) != null)
                ? getSupportFragmentManager().findFragmentByTag(FragmentLogin.TAG)
                : getSupportFragmentManager().findFragmentByTag(FragmentSignUp.TAG));

        if (f != null) {
            f.onErrorLogin();
        }
    }
}

Ask for more details if u need it.

Thank you for help and nice library ๐Ÿ‘

Host on maven central

Can we also upload this library to maven central?

Bintray has been down in the past and it seems like it is down again today and mavenCentral() is much more reliable.

AFAIK bintray can sync to maven central - there's more information here.

Proper Exceptions thrown

If a request was canceled because i.e. the user canceled the account choosing or the login, there should be a defined exception thrown. Right now, they are different depending on which requesttype you're using

Mark some or all endpoints as Authenticated without annotation

I would like to authenticate some of my requests without using the @Authenticated annotation. I'm using a code generator to generate my client API and don't have access to change the headers.

This is similar to the request here (#55) but it seems that the retroauth API has changed since.

My current solution is rewriting RetroauthCallAdapterFactory to not look for Authorized annotations like so:

override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
        for (i in callAdapterFactories.indices) {
            val adapter = callAdapterFactories[i].get(returnType, annotations, retrofit)
            adapter?.let {
                return RetroauthCallAdapter(
                    adapter as CallAdapter<Any, Any>,
                    authenticator.getTokenType(),
                    authenticator.getOwnerType(),
                    methodCache
                )
            }
        }
        return null
    }

And then setting up my dagger Retrofit module to use Retroauth or a standard Retrofit Builder based on an isAuthenticated parameter

@Module
class RetrofitModule(private val isAuthenticated: Boolean = true) {
    @Provides
    @Singleton
    fun providesRetrofit(client: OkHttpClient, app: RootApp): Retrofit {
        val moshi = Moshi.Builder().add(ZonedDateTimeAdapter()).build()
        val moshiFactory = MoshiConverterFactory.create(moshi).withNullSerialization()

        val rxJavaAdapter = RxJava2CallAdapterFactory.create()

        return if (isAuthenticated) {
            val authenticator = AccountAuthenticator()

            val cache = AndroidMethodCache()
            val callAdapter = RetroauthCallAdapterFactory(
                listOf(rxJavaAdapter),
                authenticator,
                cache
            )

            val retroauthBuilder = Retroauth.Builder(
                authenticator,
                AndroidOwnerManager(app),
                AndroidTokenStorage(app),
                cache
            )

            return retroauthBuilder
                .baseUrl("https://my.host.com/")
                .addCallAdapterFactory(callAdapter)
                .addConverterFactory(moshiFactory)
                .client(client)
                .build()
        } else {
            Retrofit.Builder().baseUrl("https://my.host.com/")
                .addCallAdapterFactory(rxJavaAdapter)
                .addConverterFactory(moshiFactory)
                .client(client)
                .build()
        }
    }
}

This is working for now, but feels kinda clunky. What's the best way to get around using annotations?

multirequest handling on invalid token

If your application is calling multiple authenticated requests((unauthenticated requests are not effected by this) at once and your token is invalid, you're getting multiple 401's and your login activity would open multiple times as well.

Wrong exception when canceling the login

When a user exists (and only then) and the token is not valid anymore (null) and the user cancels his login, there is an exception thrown which is not "OperationCanceled" as it is supposed to be.

Adding a real login to the demo

Right now, there's a mock login happening in the LoginActivity of the demo.
The disadvantage is that there's no way a 401(unauthorized) will cause the login to open, so that you cannot really see that retroauth is handling this as well.

I would like to add i.e. a facebook or similar login to the demo

Best way to implement "Anonymous" access (OAauth client_credentials)

Hi,

Up to now my app has only used this library to login a user. However, I'm now adding a "sign up" functionality, and for this I need anonymous access to my API using the OAuth client_credentials grant.

My guess is define a different token type. But it seems this library will always open a Login Activity? But I don't need an activity for this case (plus, the Login activity is already opened because the "sign up" button is in that activity).

Any suggestions?

[FORMAT]Kotlin source files need format

The source code has a strange format and incompatible with IDE. Each time when people want to add codes there're different tabs and spaces. Has the project used common Kotlin format which is provided in Android Studio ?

Login activity opens only after second launch

Hi. Awesome library, just what I needed!

I started implementing it today, learning from demos because as of 3.0.0-beta1 some parts of docs are outdated.

I have this problem:

  1. The app opens, retrofit immediately fails with an error com.andretietz.retroauth.AuthenticationCanceledException, retrying the request fails.

  2. After putting the app in background then returning back, retrying opens login activity.
    For example:
    I press back button from main activity (i.e close the app), then open it, login activity opens.
    I lock my phone then unlock and retry the request, login activity opens.
    I press home button to minimize the app, then return and retry the request, login activity opens.

  3. If I clear the app from recent apps (i.e kill the app/process), it starts from 1.

What might be the problem?
Thanks.

Possibility to use refresh tokens

If you're using this library now and your token expires, your users have to relogin to store the new token. It would be nicer if there would be a possibility to use refresh tokens, so that the user doesn't see the loginscreen again.

[BUG] In AndroidOwnerManager.kt, getOwners should not return all accounts but only of the given type

Hi

As per OwnerStorage.kt interface,
**

@param ownerType type of the owners you want to receive.
@return a list of [OWNER]s of the given type
*/
fun getOwners(ownerType: OWNER_TYPE): List

while in AndroidOwnerManager.kt, it returns all accounts. This is causing issues when Android AccountManager has multiple accounts but of different account types.

override fun getOwners(ownerType: String): List<Account> = accountManager.accounts.toList()

I think it should return accounts of specific owner type.

Retrofit 2.0 support

Retrofit 2.0 non beta should be anytime soon.
Would be nice to be supported :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.