Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add single sing on by saml #420

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/src/main/java/org/openedx/app/AppRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.openedx.core.FragmentViewType
import org.openedx.core.presentation.course.CourseViewMode
import org.openedx.core.presentation.global.appupgrade.AppUpgradeRouter
import org.openedx.core.presentation.global.appupgrade.UpgradeRequiredFragment
import org.openedx.core.presentation.global.webview.SSOWebContentFragment
import org.openedx.core.presentation.global.webview.WebContentFragment
import org.openedx.core.presentation.settings.video.VideoQualityFragment
import org.openedx.core.presentation.settings.video.VideoQualityType
Expand Down Expand Up @@ -430,6 +431,13 @@ class AppRouter :
)
}

override fun navigateToSSOWebContent(fm: FragmentManager, title: String, url: String) {
replaceFragmentWithBackStack(
fm,
SSOWebContentFragment.newInstance(title = title, url = url)
)
}

override fun navigateToManageAccount(fm: FragmentManager) {
replaceFragmentWithBackStack(fm, ManageAccountFragment())
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ val screenModule = module {
get(),
get(),
get(),
get(),
courseId,
infoType,
authCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@
.processAuthResponse()
}

suspend fun ssoLogin(
jwtToken: String
) {
if (preferencesManager.accessToken.isBlank() ||
preferencesManager.refreshToken.isBlank()){

Check warning

Code scanning / detekt

Reports missing newlines (e.g. between parentheses of a multi-line function call Warning

Missing newline before ")"

Check warning

Code scanning / detekt

Reports spaces around curly braces Warning

Missing spacing before "{"
preferencesManager.accessToken = jwtToken
preferencesManager.refreshToken = jwtToken
}
val user = api.getProfile()
preferencesManager.user = user
}

suspend fun socialLogin(token: String?, authType: AuthType) {
require(!token.isNullOrBlank()) { "Token is null" }
api.exchangeAccessToken(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ class AuthInteractor(private val repository: AuthRepository) {
repository.login(username, password)
}

suspend fun ssoLogin(
jwtToken: String
) {
repository.ssoLogin(jwtToken)
}

suspend fun loginSocial(token: String?, authType: AuthType) {
repository.socialLogin(token, authType)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ interface AuthRouter {

fun navigateToWebContent(fm: FragmentManager, title: String, url: String)

fun navigateToSSOWebContent(fm: FragmentManager, title: String, url: String)

fun clearBackStack(fm: FragmentManager)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResultListener
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.openedx.auth.data.model.AuthType
Expand Down Expand Up @@ -47,13 +48,17 @@ class SignInFragment : Fragment() {
if (viewModel.authCode != "" && !state.loginFailure && !state.loginSuccess) {
viewModel.signInAuthCode(viewModel.authCode)
}
setFragmentResultListener("requestKey") { requestKey, bundle ->
viewModel.ssoLogin(token = requestKey)
}
LoginScreen(
windowSize = windowSize,
state = state,
uiMessage = uiMessage,
onEvent = { event ->
when (event) {
is AuthEvent.SignIn -> viewModel.login(event.login, event.password)
is AuthEvent.SsoSignIn -> viewModel.ssoClicked(parentFragmentManager)
is AuthEvent.SocialSignIn -> viewModel.socialAuth(
this@SignInFragment,
event.authType
Expand Down Expand Up @@ -115,6 +120,7 @@ class SignInFragment : Fragment() {

internal sealed interface AuthEvent {
data class SignIn(val login: String, val password: String) : AuthEvent
data class SsoSignIn(val jwtToken: String) : AuthEvent
data class SocialSignIn(val authType: AuthType) : AuthEvent
data class OpenLink(val links: Map<String, String>, val link: String) : AuthEvent
object SignInBrowser : AuthEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import org.openedx.core.domain.model.RegistrationField
* @param loginSuccess is login succeed
*/
internal data class SignInUIState(
val isLoginRegistrationFormEnabled: Boolean = true,
val isSSOLoginEnabled: Boolean = false,
val ssoButtonTitle: String = "",
val isSSODefaultLoginButton: Boolean = false,
val isFacebookAuthEnabled: Boolean = false,
val isGoogleAuthEnabled: Boolean = false,
val isMicrosoftAuthEnabled: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.openedx.auth.presentation.signin

import android.content.res.Resources

Check warning

Code scanning / detekt

Detects imports in non default order Warning

Imports must be ordered in lexicographic order without any empty lines in-between with "java", "javax", "kotlin" and aliases in the end
import android.app.Activity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
Expand Down Expand Up @@ -50,6 +51,7 @@
private val appNotifier: AppNotifier,
private val analytics: AuthAnalytics,
private val oAuthHelper: OAuthHelper,
private val configuration: Config,
private val router: AuthRouter,
private val whatsNewGlobalManager: WhatsNewGlobalManager,
private val calendarPreferences: CalendarPreferences,
Expand All @@ -66,6 +68,10 @@

private val _uiState = MutableStateFlow(
SignInUIState(
isLoginRegistrationFormEnabled = config.isLoginRegistrationEnabled(),
isSSOLoginEnabled = config.isSSOLoginEnabled(),
ssoButtonTitle = config.getSSOButtonTitle(key = Resources.getSystem().getConfiguration().locales[0].language.uppercase(), ""),

Check warning

Code scanning / detekt

Reports lines with exceeded length Warning

Exceeded max line length (120)

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.

Check warning

Code scanning / detekt

Reports incorrect argument list wrapping Warning

Argument should be on a separate line (unless all arguments can fit a single line)

Check warning

Code scanning / detekt

Reports incorrect argument list wrapping Warning

Argument should be on a separate line (unless all arguments can fit a single line)

Check warning

Code scanning / detekt

Reports incorrect argument list wrapping Warning

Missing newline before ")"
isSSODefaultLoginButton = config.isSSODefaultLoginButton(),
isFacebookAuthEnabled = config.getFacebookConfig().isEnabled(),
isGoogleAuthEnabled = config.getGoogleConfig().isEnabled(),
isMicrosoftAuthEnabled = config.getMicrosoftConfig().isEnabled(),
Expand Down Expand Up @@ -141,6 +147,50 @@
}
}

fun ssoClicked(fragmentManager: FragmentManager) {
router.navigateToSSOWebContent(
fm = fragmentManager,
title = resourceManager.getString(org.openedx.core.R.string.core_sso_sign_in),
url = configuration.getSSOURL(),
)
}

fun ssoLogin(token: String) {
logEvent(AuthAnalyticsEvent.USER_SIGN_IN_CLICKED)


_uiState.update { it.copy(showProgress = true) }

Check warning

Code scanning / detekt

Reports consecutive blank lines Warning

Needless blank line(s)
viewModelScope.launch {
try {
interactor.ssoLogin("JWT $token")
_uiState.update { it.copy(loginSuccess = true) }

setUserId()
logEvent(
AuthAnalyticsEvent.SIGN_IN_SUCCESS,
buildMap {
put(
AuthAnalyticsKey.METHOD.key,
AuthType.PASSWORD.methodName.lowercase()
)
}
)
} catch (e: Exception) {
if (e is EdxError.InvalidGrantException) {
_uiMessage.value =
UIMessage.SnackBarMessage(resourceManager.getString(CoreRes.string.core_error_invalid_grant))
} else if (e.isInternetError()) {
_uiMessage.value =
UIMessage.SnackBarMessage(resourceManager.getString(CoreRes.string.core_error_no_connection))
} else {
_uiMessage.value =
UIMessage.SnackBarMessage(resourceManager.getString(CoreRes.string.core_error_unknown_error))
}
}
_uiState.update { it.copy(showProgress = false) }
}
}

private fun collectAppUpgradeEvent() {
viewModelScope.launch {
appNotifier.notifier.collect { event ->
Expand Down
Loading
Loading