@ -66,14 +66,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView ;
import androidx.recyclerview.widget.RecyclerView ;
import com.google.android.material.textfield.TextInputLayout ;
import com.google.android.material.textfield.TextInputLayout ;
import com.microsoft.identity.client.AuthenticationCallback ;
import com.microsoft.identity.client.IAuthenticationResult ;
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication ;
import com.microsoft.identity.client.IPublicClientApplication ;
import com.microsoft.identity.client.PublicClientApplication ;
import com.microsoft.identity.client.exception.MsalException ;
import net.openid.appauth.AppAuthConfiguration ;
import net.openid.appauth.AppAuthConfiguration ;
import net.openid.appauth.AuthState ;
import net.openid.appauth.AuthorizationException ;
import net.openid.appauth.AuthorizationException ;
import net.openid.appauth.AuthorizationRequest ;
import net.openid.appauth.AuthorizationRequest ;
import net.openid.appauth.AuthorizationResponse ;
import net.openid.appauth.AuthorizationResponse ;
@ -81,6 +76,7 @@ import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration ;
import net.openid.appauth.AuthorizationServiceConfiguration ;
import net.openid.appauth.ClientAuthentication ;
import net.openid.appauth.ClientAuthentication ;
import net.openid.appauth.ClientSecretPost ;
import net.openid.appauth.ClientSecretPost ;
import net.openid.appauth.NoClientAuthentication ;
import net.openid.appauth.ResponseTypeValues ;
import net.openid.appauth.ResponseTypeValues ;
import net.openid.appauth.TokenResponse ;
import net.openid.appauth.TokenResponse ;
import net.openid.appauth.browser.BrowserBlacklist ;
import net.openid.appauth.browser.BrowserBlacklist ;
@ -328,7 +324,6 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
IntentFilter iff = new IntentFilter ( ) ;
IntentFilter iff = new IntentFilter ( ) ;
iff . addAction ( ACTION_QUICK_GMAIL ) ;
iff . addAction ( ACTION_QUICK_GMAIL ) ;
iff . addAction ( ACTION_QUICK_OAUTH ) ;
iff . addAction ( ACTION_QUICK_OAUTH ) ;
iff . addAction ( ACTION_QUICK_OUTLOOK ) ;
iff . addAction ( ACTION_QUICK_SETUP ) ;
iff . addAction ( ACTION_QUICK_SETUP ) ;
iff . addAction ( ACTION_VIEW_ACCOUNTS ) ;
iff . addAction ( ACTION_VIEW_ACCOUNTS ) ;
iff . addAction ( ACTION_VIEW_IDENTITIES ) ;
iff . addAction ( ACTION_VIEW_IDENTITIES ) ;
@ -1160,273 +1155,270 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
fragmentTransaction . commit ( ) ;
fragmentTransaction . commit ( ) ;
}
}
private AuthorizationService getAuthorizationService ( ) {
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration . Builder ( )
. setBrowserMatcher ( new BrowserBlacklist (
new VersionedBrowserMatcher (
Browsers . SBrowser . PACKAGE_NAME ,
Browsers . SBrowser . SIGNATURE_SET ,
true ,
VersionRange . atMost ( "5.3" )
) ) )
. build ( ) ;
return new AuthorizationService ( this , appAuthConfig ) ;
}
private void onOAuth ( Intent intent ) {
private void onOAuth ( Intent intent ) {
String name = intent . getStringExtra ( "name" ) ;
try {
for ( EmailProvider provider : EmailProvider . loadProfiles ( this ) )
String name = intent . getStringExtra ( "name" ) ;
if ( provider . name . equals ( name ) & & provider . oauth ! = null ) {
for ( EmailProvider provider : EmailProvider . loadProfiles ( this ) )
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration . Builder ( )
if ( provider . name . equals ( name ) & & provider . oauth ! = null ) {
. setBrowserMatcher ( new BrowserBlacklist (
AuthorizationServiceConfiguration serviceConfig = new AuthorizationServiceConfiguration (
new VersionedBrowserMatcher (
Uri . parse ( provider . oauth . authorizationEndpoint ) ,
Browsers . SBrowser . PACKAGE_NAME ,
Uri . parse ( provider . oauth . tokenEndpoint ) ) ;
Browsers . SBrowser . SIGNATURE_SET ,
true ,
AuthState authState = new AuthState ( serviceConfig ) ;
VersionRange . atMost ( "5.3" )
SharedPreferences prefs = PreferenceManager . getDefaultSharedPreferences ( this ) ;
) ) )
prefs . edit ( ) . putString ( "oauth." + provider . name , authState . jsonSerializeString ( ) ) . apply ( ) ;
. build ( ) ;
AuthorizationRequest authRequest =
AuthorizationService authService = new AuthorizationService ( this , appAuthConfig ) ;
new AuthorizationRequest . Builder (
serviceConfig ,
AuthorizationRequest authRequest =
provider . oauth . clientId ,
new AuthorizationRequest . Builder (
ResponseTypeValues . CODE ,
new AuthorizationServiceConfiguration (
Uri . parse ( provider . oauth . redirectUri ) )
Uri . parse ( provider . oauth . authorizationEndpoint ) ,
. setScopes ( provider . oauth . scopes )
Uri . parse ( provider . oauth . tokenEndpoint ) ) ,
. setState ( provider . name )
provider . oauth . clientId ,
. build ( ) ;
ResponseTypeValues . CODE ,
Uri . parse ( provider . oauth . redirectUri ) )
Intent authIntent = getAuthorizationService ( ) . getAuthorizationRequestIntent ( authRequest ) ;
. setScopes ( provider . oauth . scopes )
startActivityForResult ( authIntent , REQUEST_OAUTH ) ;
. setState ( name )
. build ( ) ;
return ;
}
Intent authIntent = authService . getAuthorizationRequestIntent ( authRequest ) ;
startActivityForResult ( authIntent , REQUEST_OAUTH ) ;
return ;
}
Log . unexpectedError ( getSupportFragmentManager ( ) ,
throw new IllegalArgumentException ( "Unknown provider=" + name ) ;
new IllegalArgumentException ( "Unknown provider=" + name ) ) ;
} catch ( Throwable ex ) {
Log . unexpectedError ( getSupportFragmentManager ( ) , ex ) ;
}
}
}
private void onHandleOAuth ( Intent data ) {
private void onHandleOAuth ( @NonNull Intent data ) {
AuthorizationResponse auth = AuthorizationResponse . fromIntent ( data ) ;
try {
if ( auth = = null ) {
AuthorizationResponse auth = AuthorizationResponse . fromIntent ( data ) ;
AuthorizationException ex = AuthorizationException . fromIntent ( data ) ;
if ( auth = = null )
throw AuthorizationException . fromIntent ( data ) ;
for ( EmailProvider provider : EmailProvider . loadProfiles ( this ) )
if ( provider . name . equals ( auth . state ) ) {
SharedPreferences prefs = PreferenceManager . getDefaultSharedPreferences ( this ) ;
final AuthState authState = AuthState . jsonDeserialize ( prefs . getString ( "oauth." + provider . name , null ) ) ;
authState . update ( auth , null ) ;
prefs . edit ( ) . remove ( "oauth." + provider . name ) . apply ( ) ;
ClientAuthentication clientAuth ;
if ( provider . oauth . clientSecret = = null )
clientAuth = NoClientAuthentication . INSTANCE ;
else
clientAuth = new ClientSecretPost ( provider . oauth . clientSecret ) ;
getAuthorizationService ( ) . performTokenRequest (
auth . createTokenExchangeRequest ( ) ,
clientAuth ,
new AuthorizationService . TokenResponseCallback ( ) {
@Override
public void onTokenRequestCompleted ( TokenResponse access , AuthorizationException error ) {
try {
if ( access = = null )
throw error ;
authState . update ( access , null ) ;
Log . i ( "OAuth token provider=" + provider . name ) ;
if ( "Outlook/Office365" . equals ( provider . name ) ) {
authState . performActionWithFreshTokens ( getAuthorizationService ( ) , new AuthState . AuthStateAction ( ) {
@Override
public void execute ( String accessToken , String idToken , AuthorizationException error ) {
try {
if ( error ! = null )
throw error ;
onOutlook ( accessToken , idToken ) ;
} catch ( Throwable ex ) {
Log . unexpectedError ( getSupportFragmentManager ( ) , ex ) ;
}
}
} ) ;
} else
throw new IllegalArgumentException ( "Unknown action provider=" + provider . name ) ;
} catch ( Throwable ex ) {
Log . unexpectedError ( getSupportFragmentManager ( ) , ex ) ;
}
}
} ) ;
return ;
}
throw new IllegalArgumentException ( "Unknown state=" + auth . state ) ;
} catch ( Throwable ex ) {
Log . unexpectedError ( getSupportFragmentManager ( ) , ex ) ;
Log . unexpectedError ( getSupportFragmentManager ( ) , ex ) ;
return ;
}
}
}
for ( EmailProvider provider : EmailProvider . loadProfiles ( this ) )
private void onOutlook ( String accessToken , String idToken ) {
if ( provider . name . equals ( auth . state ) ) {
Bundle args = new Bundle ( ) ;
AuthorizationService authService = new AuthorizationService ( this ) ;
args . putString ( "token" , accessToken ) ;
ClientAuthentication clientAuth = new ClientSecretPost ( provider . oauth . clientSecret ) ;
authService . performTokenRequest (
auth . createTokenExchangeRequest ( ) ,
clientAuth ,
new AuthorizationService . TokenResponseCallback ( ) {
@Override
public void onTokenRequestCompleted ( TokenResponse access , AuthorizationException ex ) {
if ( access = = null ) {
Log . unexpectedError ( getSupportFragmentManager ( ) , ex ) ;
return ;
}
// access.accessToken
new SimpleTask < JSONObject > ( ) {
}
@Override
} ) ;
protected JSONObject onExecute ( Context context , Bundle args ) throws Throwable {
String token = args . getString ( "token" ) ;
// https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http#http-request
URL url = new URL ( "https://graph.microsoft.com/v1.0/me" +
"?$select=displayName,otherMails" ) ;
Log . i ( "MSGraph fetching " + url ) ;
HttpURLConnection request = ( HttpURLConnection ) url . openConnection ( ) ;
request . setReadTimeout ( 15 * 1000 ) ;
request . setConnectTimeout ( 15 * 1000 ) ;
request . setRequestMethod ( "GET" ) ;
request . setDoInput ( true ) ;
request . setRequestProperty ( "Authorization" , "Bearer " + token ) ;
request . setRequestProperty ( "Content-Type" , "application/json" ) ;
request . connect ( ) ;
return ;
try {
Log . i ( "MSGraph getting response" ) ;
String json = Helper . readStream ( request . getInputStream ( ) , StandardCharsets . UTF_8 . name ( ) ) ;
return new JSONObject ( json ) ;
} finally {
request . disconnect ( ) ;
}
}
}
Log . unexpectedError ( getSupportFragmentManager ( ) ,
@Override
new IllegalArgumentException ( "Unknown state=" + auth . state ) ) ;
protected void onExecuted ( Bundle args , JSONObject data ) {
}
Log . i ( "MSGraph " + data ) ;
private void onOutlook ( Intent intent ) {
try {
PublicClientApplication . createMultipleAccountPublicClientApplication (
JSONArray otherMails = data . getJSONArray ( "otherMails" ) ;
this ,
R . raw . msal_config ,
new IPublicClientApplication . IMultipleAccountApplicationCreatedListener ( ) {
@Override
public void onCreated ( IMultipleAccountPublicClientApplication msal ) {
Log . i ( "MSAL app created" ) ;
msal . acquireToken (
ActivitySetup . this ,
// "openid", "offline_access", "profile", "email"
// https://docs.microsoft.com/en-us/graph/permissions-reference
new String [ ] {
"openid" , "offline_access" , "profile" , "email" ,
"User.Read" , "Mail.ReadWrite" , "Mail.Send" , "MailboxSettings.ReadWrite" } ,
new AuthenticationCallback ( ) {
@Override
public void onSuccess ( IAuthenticationResult result ) {
Log . i ( "MSAL got token" ) ;
Bundle args = new Bundle ( ) ;
args . putString ( "token" , result . getAccessToken ( ) ) ;
args . putString ( "id" , result . getAccount ( ) . getId ( ) ) ;
args . putString ( "tenant" , result . getAccount ( ) . getTenantId ( ) ) ;
Log . logBundle ( args ) ;
Map < String , ? > claims = result . getAccount ( ) . getClaims ( ) ;
if ( claims ! = null )
for ( String key : claims . keySet ( ) )
Log . i ( key + "=" + claims . get ( key ) ) ;
new SimpleTask < JSONObject > ( ) {
@Override
protected JSONObject onExecute ( Context context , Bundle args ) throws Throwable {
String token = args . getString ( "token" ) ;
// https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http#http-request
URL url = new URL ( "https://graph.microsoft.com/v1.0/me" +
"?$select=displayName,otherMails" ) ;
Log . i ( "MSAL fetching " + url ) ;
HttpURLConnection request = ( HttpURLConnection ) url . openConnection ( ) ;
request . setReadTimeout ( 15 * 1000 ) ;
request . setConnectTimeout ( 15 * 1000 ) ;
request . setRequestMethod ( "GET" ) ;
request . setDoInput ( true ) ;
request . setRequestProperty ( "Authorization" , "Bearer " + token ) ;
request . setRequestProperty ( "Content-Type" , "application/json" ) ;
request . connect ( ) ;
try {
Log . i ( "MSAL getting response" ) ;
String json = Helper . readStream ( request . getInputStream ( ) , StandardCharsets . UTF_8 . name ( ) ) ;
return new JSONObject ( json ) ;
} finally {
request . disconnect ( ) ;
}
}
@Override
protected void onExecuted ( Bundle args , JSONObject data ) {
Log . i ( "MSAL " + data ) ;
try {
JSONArray otherMails = data . getJSONArray ( "otherMails" ) ;
args . putString ( "displayName" , data . getString ( "displayName" ) ) ;
args . putString ( "email" , ( String ) otherMails . get ( 0 ) ) ;
new SimpleTask < Void > ( ) {
@Override
protected Void onExecute ( Context context , Bundle args ) throws Throwable {
String token = args . getString ( "token" ) ;
String email = args . getString ( "email" ) ;
String displayName = args . getString ( "displayName" ) ;
List < EntityFolder > folders ;
// https://msdn.microsoft.com/en-us/windows/desktop/dn440163
String host = "imap-mail.outlook.com" ;
int port = 993 ;
boolean starttls = false ;
String user = email ;
String password = token ;
try ( MailService iservice = new MailService ( context , "imaps" , null , false , true , true ) ) {
iservice . connect ( host , port , MailService . AUTH_TYPE_OUTLOOK , user , password , null ) ;
folders = iservice . getFolders ( ) ;
DB db = DB . getInstance ( context ) ;
try {
db . beginTransaction ( ) ;
EntityAccount primary = db . account ( ) . getPrimaryAccount ( ) ;
// Create account
EntityAccount account = new EntityAccount ( ) ;
account . host = host ;
account . starttls = starttls ;
account . port = port ;
account . auth_type = MailService . AUTH_TYPE_OUTLOOK ;
account . user = user ;
account . password = password ;
account . name = "OutLook" ;
account . synchronize = true ;
account . primary = ( primary = = null ) ;
account . created = new Date ( ) . getTime ( ) ;
account . last_connected = account . created ;
account . id = db . account ( ) . insertAccount ( account ) ;
args . putLong ( "account" , account . id ) ;
EntityLog . log ( context , "OutLook account=" + account . name ) ;
// Create folders
for ( EntityFolder folder : folders ) {
folder . account = account . id ;
folder . id = db . folder ( ) . insertFolder ( folder ) ;
EntityLog . log ( context , "OutLook folder=" + folder . name + " type=" + folder . type ) ;
}
// Set swipe left/right folder
for ( EntityFolder folder : folders )
if ( EntityFolder . TRASH . equals ( folder . type ) )
account . swipe_left = folder . id ;
else if ( EntityFolder . ARCHIVE . equals ( folder . type ) )
account . swipe_right = folder . id ;
db . account ( ) . updateAccount ( account ) ;
// Create identity
EntityIdentity identity = new EntityIdentity ( ) ;
identity . name = displayName ;
identity . email = user ;
identity . account = account . id ;
identity . host = "smtp-mail.outlook.com" ;
identity . starttls = true ;
identity . port = 587 ;
identity . auth_type = MailService . AUTH_TYPE_OUTLOOK ;
identity . user = user ;
identity . password = password ;
identity . synchronize = true ;
identity . primary = true ;
identity . id = db . identity ( ) . insertIdentity ( identity ) ;
args . putLong ( "identity" , identity . id ) ;
EntityLog . log ( context , "Gmail identity=" + identity . name + " email=" + identity . email ) ;
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
return null ;
}
@Override
protected void onException ( Bundle args , Throwable ex ) {
}
} . execute ( ActivitySetup . this , args , "outlook:account" ) ;
} catch ( JSONException ex ) {
Log . e ( ex ) ;
}
}
@Override
args . putString ( "displayName" , data . getString ( "displayName" ) ) ;
protected void onException ( Bundle args , Throwable ex ) {
args . putString ( "email" , ( String ) otherMails . get ( 0 ) ) ;
Log . unexpectedError ( getSupportFragmentManager ( ) , ex ) ;
}
} . execute ( ActivitySetup . this , args , "graph:profile" ) ;
}
@Override
new SimpleTask < Void > ( ) {
public void onError ( MsalException ex ) {
@Override
Log . e ( ex ) ;
protected Void onExecute ( Context context , Bundle args ) throws Throwable {
}
String token = args . getString ( "token" ) ;
String email = args . getString ( "email" ) ;
String displayName = args . getString ( "displayName" ) ;
List < EntityFolder > folders ;
// https://msdn.microsoft.com/en-us/windows/desktop/dn440163
String host = "imap-mail.outlook.com" ;
int port = 993 ;
boolean starttls = false ;
String user = email ;
String password = token ;
try ( MailService iservice = new MailService ( context , "imaps" , null , false , true , true ) ) {
iservice . connect ( host , port , MailService . AUTH_TYPE_OUTLOOK , user , password , null ) ;
folders = iservice . getFolders ( ) ;
DB db = DB . getInstance ( context ) ;
try {
db . beginTransaction ( ) ;
@Override
EntityAccount primary = db . account ( ) . getPrimaryAccount ( ) ;
public void onCancel ( ) {
Log . w ( "MSAL cancelled" ) ;
// Create account
EntityAccount account = new EntityAccount ( ) ;
account . host = host ;
account . starttls = starttls ;
account . port = port ;
account . auth_type = MailService . AUTH_TYPE_OUTLOOK ;
account . user = user ;
account . password = password ;
account . name = "OutLook" ;
account . synchronize = true ;
account . primary = ( primary = = null ) ;
account . created = new Date ( ) . getTime ( ) ;
account . last_connected = account . created ;
account . id = db . account ( ) . insertAccount ( account ) ;
args . putLong ( "account" , account . id ) ;
EntityLog . log ( context , "OutLook account=" + account . name ) ;
// Create folders
for ( EntityFolder folder : folders ) {
folder . account = account . id ;
folder . id = db . folder ( ) . insertFolder ( folder ) ;
EntityLog . log ( context , "OutLook folder=" + folder . name + " type=" + folder . type ) ;
}
}
} ) ;
}
@Override
// Set swipe left/right folder
public void onError ( MsalException ex ) {
for ( EntityFolder folder : folders )
Log . e ( "MSAL" , ex ) ;
if ( EntityFolder . TRASH . equals ( folder . type ) )
}
account . swipe_left = folder . id ;
} ) ;
else if ( EntityFolder . ARCHIVE . equals ( folder . type ) )
account . swipe_right = folder . id ;
db . account ( ) . updateAccount ( account ) ;
// Create identity
EntityIdentity identity = new EntityIdentity ( ) ;
identity . name = displayName ;
identity . email = user ;
identity . account = account . id ;
identity . host = "smtp-mail.outlook.com" ;
identity . starttls = true ;
identity . port = 587 ;
identity . auth_type = MailService . AUTH_TYPE_OUTLOOK ;
identity . user = user ;
identity . password = password ;
identity . synchronize = true ;
identity . primary = true ;
identity . id = db . identity ( ) . insertIdentity ( identity ) ;
args . putLong ( "identity" , identity . id ) ;
EntityLog . log ( context , "Gmail identity=" + identity . name + " email=" + identity . email ) ;
db . setTransactionSuccessful ( ) ;
} finally {
db . endTransaction ( ) ;
}
}
return null ;
}
@Override
protected void onException ( Bundle args , Throwable ex ) {
}
} . execute ( ActivitySetup . this , args , "outlook:account" ) ;
} catch ( JSONException ex ) {
Log . e ( ex ) ;
}
}
@Override
protected void onException ( Bundle args , Throwable ex ) {
Log . unexpectedError ( getSupportFragmentManager ( ) , ex ) ;
}
} . execute ( ActivitySetup . this , args , "graph:profile" ) ;
}
}
private void onViewQuickSetup ( Intent intent ) {
private void onViewQuickSetup ( Intent intent ) {
@ -1580,8 +1572,6 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
onGmail ( intent ) ;
onGmail ( intent ) ;
else if ( ACTION_QUICK_OAUTH . equals ( action ) )
else if ( ACTION_QUICK_OAUTH . equals ( action ) )
onOAuth ( intent ) ;
onOAuth ( intent ) ;
else if ( ACTION_QUICK_OUTLOOK . equals ( action ) )
onOutlook ( intent ) ;
else if ( ACTION_QUICK_SETUP . equals ( action ) )
else if ( ACTION_QUICK_SETUP . equals ( action ) )
onViewQuickSetup ( intent ) ;
onViewQuickSetup ( intent ) ;
else if ( ACTION_VIEW_ACCOUNTS . equals ( action ) )
else if ( ACTION_VIEW_ACCOUNTS . equals ( action ) )