andretietz / retroauth Goto Github PK
View Code? Open in Web Editor NEWA library build on top of retrofit, for simple handling of authenticated requests
License: Apache License 2.0
A library build on top of retrofit, for simple handling of authenticated requests
License: Apache License 2.0
AuthenticationCanceledException will be thrown in case of any another exception will be thrown from response = chain.proceed(request);
at CredentialInterceptor.java:71
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?
I'm using Retroauth in a library (core) project. The trouble im getting is that I cannot define the string.xml resources because a library project's strings are not final/constants. Is there any way of getting around this? (or modifying the project a bit)
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?
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?
To make clear what happens when, it would be nice to have a sequence diagram, to see what happens when
The main functionality of the requests should be tested with either unit- or instrumentation tests
Unsafe use of a nullable receiver of type Context?
Kotlin-plugin: 1.2.61
AS Gradle: 3.3.0
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
Is there a way to hide my custom account used by retroauth from the android Accounts configurations?
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).
Hi, great library !
I think that would be useful if I could annotate a service as authorized and mark the unauthorized methods as anonymous, It would be possible ?
Thanks
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)
.
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:
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
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);
}
}
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 :)
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 !
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.
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)
Each class and at least all public methods of those, should provide a helpful documentation
Hey,
I'm not sure if it's related to retroauth or not, but I have this edge case issue:
@Authenticated
auth error
, because the token is no more valid (or some other token related error)ownerManager.getActiveOwner()
, ownerManager.removeOwner()
), opens LoginActivity with clear topauth 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.
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
hi Unic8
i'm using your library but i can't start my activity that extends your authenticationactivity because of this error : Caused by: java.lang.RuntimeException: This Activity cannot be started without the "accountType" extra in the intent!.
I hope you now the reason
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".
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.
AuthenticationActivity
seems to be outdated)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
Unsafe use of a nullable receiver of type Window?
Kotlin-plugin: 1.2.61
AS Gradle: 3.3.0
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 ๐
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.
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
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?
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.
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.
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
I'm currently making a authenticated call using Retrofit2 in a SyncAdapter. This Adapter runs in the background, so it fails when the app is not open (because there are no activities).
Any solution for this?
Version 2.0 will automatically manage and use the refresh_token the refresh access_token?
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?
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 ?
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:
The app opens, retrofit immediately fails with an error com.andretietz.retroauth.AuthenticationCanceledException
, retrying the request fails.
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.
If I clear the app from recent apps (i.e kill the app/process), it starts from 1.
What might be the problem?
Thanks.
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.
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.
I think it should return accounts of specific owner type.
retroauth/gradle/publish.gradle
Line 20 in 8b5c003
Retrofit 2.0 non beta should be anytime soon.
Would be nice to be supported :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.