Skip to content

Instantly share code, notes, and snippets.

@lbalmaceda
Last active March 3, 2016 00:48
Show Gist options
  • Save lbalmaceda/2f0df8b7d773f5f75bfb to your computer and use it in GitHub Desktop.
Save lbalmaceda/2f0df8b7d773f5f75bfb to your computer and use it in GitHub Desktop.
CustomIdentityProvider accepts checking for granted permissions before starting the authentication flow.
/**
* Interface for allowing an IdentityProvider to request specific permissions
* before calling start.
*/
public abstract class AuthorizedIdentityProvider implements IdentityProvider, PermissionCallback {
protected IdentityProvider idp;
public AuthorizedIdentityProvider(@NonNull IdentityProvider idp){
this.idp = idp;
}
@Override
public void setCallback(IdentityProviderCallback callback){
idp.setCallback(callback);
}
@Override
public void start(Activity activity, @NonNull String connectionName){
checkPermissions(activity, connectionName);
}
@Override
public void stop(){
idp.stop();
}
@Override
public boolean authorize(Activity activity, @NonNull AuthorizeResult result){
idp.authorize(activity, result);
}
@Override
public void clearSession(){
idp.clearSession();
}
/**
* Defines which permissions are required by this Identity Provider to work
*
* @return the required permissions
*/
abstract String[] getRequiredPermissions();
/**
* Checks if the required permissions have been granted by the user and starts the authentication process
*
* @param activity activity that starts the process (and will receive its response)
* @param connectionName of the IdP to authenticate with
*/
private void checkPermissions(Activity activity, @NonNull String connectionName) {
String[] permissions = getRequiredPermissions();
if (permissions.length == 0) {
return idp.start(activity, connectionName);
}
PermissionHandler handler = new PermissionHandler(activity, this);
if (handler.areAllPermissionGranted(permissions)) {
idp.start(activity, connectionName);
} else {
handler.requestPermissions(permissions);
}
}
@Override
public void onPermissionsResult(String[] permissionsGranted, String[] permissionsDeclined) {
if (permissionsDeclined.length == 0) {
//continue with the Authentication flow
idp.start(activity, serviceName);
}
}
}
/**
* Sample implementation of an IdentityProvider that requires permissions
*/
public class CustomIdentityProvider implements IdentityProvider {
public static final String TAG = CustomIdentityProvider.class.getName();
private Activity activity;
private IdentityProviderCallback callback;
public CustomIdentityProvider(Context context) {
//Init the custom native api client
}
@Override
public void setCallback(IdentityProviderCallback callback) {
this.callback = callback;
}
@Override
public String[] getRequiredPermissions() {
return new String[]{Manifest.permission.GET_ACCOUNTS};
}
@Override
public void start(Activity activity, String serviceName) {
//Blah
}
@Override
public void stop() {
clearSession();
}
@Override
public boolean authorize(Activity activity, @NonNull AuthorizeResult result) {
//Blah
return false;
}
@Override
public void clearSession() {
//Blah
}
@Override
public void onPermissionRequireExplanation(String permission) {
//Show dialog explaining why we need the permission.
//Then call checkPermissionsAndStart again
}
}
/**
* Interface for the object that can handle authentication against an Identity Provider.
*/
public interface IdentityProvider extends PermissionCallback {
static final int WEBVIEW_AUTH_REQUEST_CODE = 500;
static final int GOOGLE_PLUS_REQUEST_CODE = 501;
static final int GOOGLE_PLUS_TOKEN_REQUEST_CODE = 502;
/**
* Sets the callback that will report the result of the authentication
*
* @param callback a callback
*/
void setCallback(IdentityProviderCallback callback);
/**
* Starts the authentication process for an identity provider.
*
* @param activity activity that starts the process (and will receive its response)
* @param connectionName of the IdP to authenticate with
*/
void start(Activity activity, @NonNull String connectionName);
/**
* Stops the authentication process (even if it's in progress).
*/
void stop();
/**
* Called with the result of the authorization.
*
* @param activity activity that will receive the result.
* @param result authorization result data.
* @return if the result is valid or not.
*/
boolean authorize(Activity activity, @NonNull AuthorizeResult result);
/**
* Removes any session information stored in the object.
*/
void clearSession();
}
/*
* PermissionCallback.java
*
* Copyright (c) 2016 Auth0 (http://auth0.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.auth0.android.lock.provider;
public interface PermissionCallback {
/**
* Called when a permission was previously declined by the user and requires explanation
*
* @param permission to explain to the user
*/
void onPermissionRequireExplanation(String permission);
/**
* Called when a permission request is accepted by the user.
*
* @param permissionsGranted the user just accepted.
* @param permissionsDeclined the user just declined.
*/
void onPermissionsResult(String[] permissionsGranted, String[] permissionsDeclined);
}
package com.auth0.android.lock.provider;
import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.PermissionChecker;
public class PermissionHandler implements ActivityCompat.OnRequestPermissionsResultCallback {
private static final int PERMISSION_REQ_CODE = 324;
private final Activity context;
private final IdentityProvider callback;
public PermissionHandler(@NonNull Activity context, @NonNull IdentityProvider callback) {
this.context = context;
this.callback = callback;
}
/**
* Checks if the given permission has been granted by the user to this application before.
*
* @param permission to check availability for
* @return true if the permission is currently granted, false otherwise.
*/
public boolean isPermissionGranted(@NonNull String permission) {
int result = ContextCompat.checkSelfPermission(context, permission);
return result == PermissionChecker.PERMISSION_GRANTED;
}
/**
* Checks if the given permissions have been granted by the user to this application before.
*
* @param permissions to check availability for
* @return true if all the permissions are currently granted, false otherwise.
*/
public boolean areAllPermissionGranted(@NonNull String[] permissions) {
for (String p : permissions) {
if (!isPermissionGranted(p)) {
return false;
}
}
return true;
}
/**
* Starts the async request of the given permission.
*
* @param permissions to request to the user
*/
public void requestPermissions(@NonNull String[] permissions) {
boolean explanationRequired = false;
for (String p : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(context, p)) {
explanationRequired = true;
callback.onPermissionRequireExplanation(p);
}
}
if (explanationRequired) {
return;
}
ActivityCompat.requestPermissions(context,
permissions,
PERMISSION_REQ_CODE);
}
/**
* Called when there is a new response for a Permission request
*
* @param requestCode received
* @param permissions the permissions that were requested
* @param grantResults the grant result for each permission
*/
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode != PERMISSION_REQ_CODE) {
return;
} else if (permissions.length == 0 && grantResults.length == 0) {
callback.onPermissionsResult(new String[]{}, permissions);
return;
}
int grantedIndex = 0;
int declinedIndex = 0;
String[] permissionsGranted = new String[]{};
String[] permissionsDeclined = new String[]{};
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PermissionChecker.PERMISSION_GRANTED) {
permissionsGranted[grantedIndex] = permissions[i];
grantedIndex++;
} else {
permissionsDeclined[declinedIndex] = permissions[i];
declinedIndex++;
}
}
callback.onPermissionsResult(permissionsGranted, permissionsDeclined);
}
}

AuthorizedIdentityProvider

How to use it

  • Keep your existing IdentityProvider.
  • Create a new class extending AuthorizedIdentityProvider.
  • When creating a new instance of this class, pass to the constructor the instance of your previous IdentityProvider implementation for wrap purpose. This is know as the Decorator Pattern
  • Implement the abstract method getRequiredPermissions() to define which manifest permissions are mandatory for your Identity Provider to work.
  • Implement the onPermissionRequireExplanation(String permission) to let the user know whats your need and how are you going to use that permission.
  • You can override the methods defined in the IdentityProvider interface as needed, always calling super for the permission request logic to work.
  • When all the permissions are granted by the user, the Authentication flow will work as usual.

Advantages

  • Keeps the implementation of the Permission requests in one place.
  • Because it acts as a wrapper, the previous IdentityProvider implementation does not need to change.

Drawbacks

  • Users will need to create this new class to wrap the previous one and define required permissions, for each already existing IdentityProvider that requires a manifest permission.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment