Skip to content

Commit

Permalink
Merge pull request #12 from onelogin/add_signout_route
Browse files Browse the repository at this point in the history
Initial code for sign out
  • Loading branch information
bzvestey authored Mar 18, 2022
2 parents def67ea + 8ac1b12 commit 941e048
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 7 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ android {

defaultConfig {
applicationId "com.onelogin.oidc.demo"
minSdkVersion 19
minSdkVersion 26
targetSdkVersion 29
versionCode 1
versionName "1.0"
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/java/com/onelogin/oidc/demo/UserFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import androidx.navigation.Navigation
import com.google.android.material.snackbar.Snackbar
import com.onelogin.oidc.Callback
import com.onelogin.oidc.OneLoginOIDC
import com.onelogin.oidc.logout.SignOutError
import com.onelogin.oidc.logout.SignOutSuccess
import com.onelogin.oidc.refresh.RefreshError
import com.onelogin.oidc.refresh.RefreshSuccess
import com.onelogin.oidc.revoke.RevokeError
Expand Down Expand Up @@ -56,8 +58,8 @@ class UserFragment : Fragment(),
}

private fun logout() {
oidcClient.revokeToken(object : Callback<RevokeSuccess, RevokeError> {
override fun onSuccess(success: RevokeSuccess) {
oidcClient.signOut(requireActivity(), object : Callback<SignOutSuccess, SignOutError> {
override fun onSuccess(success: SignOutSuccess) {
val action = UserFragmentDirections.actionUserFragmentToSignInFragment()
Navigation.findNavController(requireView()).navigate(action)
Snackbar.make(
Expand All @@ -67,7 +69,7 @@ class UserFragment : Fragment(),
).show()
}

override fun onError(error: RevokeError) {
override fun onError(error: SignOutError) {
Snackbar.make(
requireView(),
getString(R.string.error_logging_out, error.message),
Expand Down
2 changes: 1 addition & 1 deletion oneloginoidc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'net.openid:appauth:0.7.1'
implementation 'net.openid:appauth:0.11.1'
implementation 'com.squareup.okhttp3:okhttp:4.5.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
Expand Down
10 changes: 10 additions & 0 deletions oneloginoidc/src/main/java/com/onelogin/oidc/OIDCClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.onelogin.oidc.introspect.IntrospectionError
import com.onelogin.oidc.introspect.TokenIntrospection
import com.onelogin.oidc.login.SignInError
import com.onelogin.oidc.login.SignInSuccess
import com.onelogin.oidc.logout.SignOutError
import com.onelogin.oidc.logout.SignOutSuccess
import com.onelogin.oidc.refresh.RefreshError
import com.onelogin.oidc.refresh.RefreshSuccess
import com.onelogin.oidc.revoke.RevokeError
Expand All @@ -25,6 +27,14 @@ interface OIDCClient {
*/
fun signIn(activity: Activity, signInCallback: Callback<SignInSuccess, SignInError>)

/**
* Call this method in order to execute the sign out process, which sign the user out.
*
* @param activity Activity that will own the sign out process, process will be attached to its lifecycle
* @param signOutCallback Callback to receive the status of the signOut process.
*/
fun signOut(activity: Activity, signOutCallback: Callback<SignOutSuccess, SignOutError>)

/**
* Call this method in order to invalidate a previous session and all the tokens related to this,
* when we logout the user we should call this method in order to ensure that the tokens can still be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import com.onelogin.oidc.data.stores.OneLoginStore
import com.onelogin.oidc.login.SignInFragment
import com.onelogin.oidc.login.SignInFragment.Companion.ARG_AUTHORIZATION_REQUEST
import com.onelogin.oidc.login.SignInManagerImpl
import com.onelogin.oidc.logout.SignOutFragment
import com.onelogin.oidc.logout.SignOutFragment.Companion.ARG_END_SESSION_REQUEST
import com.onelogin.oidc.logout.SignOutManagerImpl

internal class OIDCClientFactory(
private val context: Context,
Expand Down Expand Up @@ -43,6 +46,17 @@ internal class OIDCClientFactory(
fragment
}

return OIDCClientImpl(authorizationService, networkClient, repository, signInManager)
val signOutManager = SignOutManagerImpl(
configuration,
repository
) { endSessionRequest ->
val fragment = SignOutFragment()
val args = Bundle()
args.putString(ARG_END_SESSION_REQUEST, endSessionRequest.jsonSerializeString())
fragment.arguments = args
fragment
}

return OIDCClientImpl(authorizationService, networkClient, repository, signInManager, signOutManager)
}
}
22 changes: 21 additions & 1 deletion oneloginoidc/src/main/java/com/onelogin/oidc/OIDCClientImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import com.onelogin.oidc.introspect.TokenIntrospection
import com.onelogin.oidc.login.SignInError
import com.onelogin.oidc.login.SignInManager
import com.onelogin.oidc.login.SignInSuccess
import com.onelogin.oidc.logout.SignOutError
import com.onelogin.oidc.logout.SignOutManager
import com.onelogin.oidc.logout.SignOutSuccess
import com.onelogin.oidc.refresh.RefreshError
import com.onelogin.oidc.refresh.RefreshSuccess
import com.onelogin.oidc.revoke.RevokeError
Expand All @@ -29,7 +32,8 @@ internal class OIDCClientImpl(
private val authorizationService: AuthorizationService,
private val networkClient: NetworkClient,
private val repository: OIDCRepository,
private val signInManager: SignInManager
private val signInManager: SignInManager,
private val signOutManager: SignOutManager
) : OIDCClient {

private var currentActivity: WeakReference<Activity>? = null
Expand All @@ -44,6 +48,22 @@ internal class OIDCClientImpl(
}
}

override fun signOut(activity: Activity, signOutCallback: Callback<SignOutSuccess, SignOutError>) {
repository.getLatestAuthState().let { state ->
state.performActionWithFreshTokens(authorizationService) { _, idToken, ex ->
if (ex != null) {
signOutCallback.onError((SignOutError(ex.message, ex)))
} else if (idToken != null) {
listenActivityLifecycle(activity)
activityScope.coroutineContext.cancelChildren()
activityScope.launch {
signOutManager.signOut(idToken, activity, signOutCallback)
}
}
}
}
}

override fun revokeToken(revokeTokenCallback: Callback<RevokeSuccess, RevokeError>) {
repository.getLatestAuthState().let { state ->
state.performActionWithFreshTokens(authorizationService) { accessToken, _, ex ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.onelogin.oidc.logout

class SignOutError(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.onelogin.oidc.logout

import android.content.Intent
import androidx.fragment.app.Fragment
import com.onelogin.oidc.data.AuthorizationServiceProvider
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedSendChannelException
import net.openid.appauth.AuthorizationException
import net.openid.appauth.EndSessionRequest
import net.openid.appauth.EndSessionResponse
import timber.log.Timber

class SignOutFragment : Fragment() {

internal val resultChannel = Channel<Pair<EndSessionResponse?, AuthorizationException?>>()

override fun onResume() {
super.onResume()
val authorizationRequestString = arguments?.getString(ARG_END_SESSION_REQUEST)
val authorizationRequest = authorizationRequestString?.let { EndSessionRequest.jsonDeserialize(authorizationRequestString) }
authorizationRequest?.let {
val authIntent = AuthorizationServiceProvider.authorizationService.getEndSessionRequestIntent(it)
startActivityForResult(authIntent, END_SESSION_REQUEST_CODE)
arguments?.putString(ARG_END_SESSION_REQUEST, null)
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == END_SESSION_REQUEST_CODE) {
data?.let {
val authorizationResponse = EndSessionResponse.fromIntent(data)
val exception = AuthorizationException.fromIntent(data)
try {
resultChannel.offer(authorizationResponse to exception)
resultChannel.close()
} catch (e: ClosedSendChannelException) {
Timber.d("Could not deliver logout result")
}
}
}
}

override fun onDestroy() {
super.onDestroy()
resultChannel.close()
}

companion object {
internal const val END_SESSION_REQUEST_CODE = 34001
internal const val ARG_END_SESSION_REQUEST = "end_session_request"
internal const val LOGOUT_FRAGMENT_TAG = "logout_fragment"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.onelogin.oidc.logout

import android.app.Activity
import com.onelogin.oidc.Callback

interface SignOutManager {
suspend fun signOut(
idToken: String,
activity: Activity,
signOutCallback: Callback<SignOutSuccess, SignOutError>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.onelogin.oidc.logout

import android.app.Activity
import android.net.Uri
import androidx.fragment.app.FragmentActivity
import com.onelogin.oidc.Callback
import com.onelogin.oidc.OIDCConfiguration
import com.onelogin.oidc.data.repository.OIDCRepository
import kotlinx.coroutines.channels.consumeEach
import net.openid.appauth.AuthorizationServiceConfiguration
import net.openid.appauth.EndSessionRequest

internal class SignOutManagerImpl(
private val configuration: OIDCConfiguration,
private val repository: OIDCRepository,
private val signOutFragmentProvider: (EndSessionRequest) -> SignOutFragment
) : SignOutManager {

override suspend fun signOut(
idToken: String,
activity: Activity,
signOutCallback: Callback<SignOutSuccess, SignOutError>
) {
val authConfiguration = repository.getConfigurations()
val endSessionRequest = getEndSessionRequest(idToken, configuration, authConfiguration)

if (activity is FragmentActivity) {
removeFragmentIfAttached(activity)
val logoutFragment = signOutFragmentProvider(endSessionRequest)
attachLogoutFragment(activity, logoutFragment)
logoutFragment.resultChannel.consumeEach { (response, exception) ->
if (exception != null) {
signOutCallback.onError(SignOutError(exception.message, exception))
} else if (response != null) {
repository.clearAuthState()
signOutCallback.onSuccess(SignOutSuccess("Success"))
}
}
removeFragmentIfAttached(activity)
} else {
throw IllegalStateException("Your activity should extend FragmentActivity or AppCompatActivity")
}
}

private fun attachLogoutFragment(
activity: FragmentActivity,
loginFragment: SignOutFragment
) {
activity.supportFragmentManager.beginTransaction()
.add(loginFragment, SignOutFragment.LOGOUT_FRAGMENT_TAG)
.commit()
}

private fun removeFragmentIfAttached(activity: FragmentActivity) {
activity.supportFragmentManager.findFragmentByTag(SignOutFragment.LOGOUT_FRAGMENT_TAG)?.let {
activity.supportFragmentManager.beginTransaction()
.remove(it)
.commit()
}
}

private fun getEndSessionRequest(
idToken: String,
configuration: OIDCConfiguration,
authConfiguration: AuthorizationServiceConfiguration
): EndSessionRequest {
val issuerUrl = Uri.parse(configuration.issuer)

val endSessionReqBuilder = EndSessionRequest.Builder(authConfiguration)
.setIdTokenHint(idToken)
.setPostLogoutRedirectUri(Uri.parse(configuration.redirectUrl))

return endSessionReqBuilder.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.onelogin.oidc.logout

data class SignOutSuccess(
val status: String
)

0 comments on commit 941e048

Please sign in to comment.