-
Notifications
You must be signed in to change notification settings - Fork 23
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: Support for login and registration via a browser custom tab #371
base: main
Are you sure you want to change the base?
Conversation
This change adds support for logging in and registering a new account using the browser. This can be useful for cases where the only way to log into the instatance is via a custom third-party auth provider. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8020f6b..e2c208d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,6 +45,12 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="${applicationId}" /> + </intent-filter> <!-- Branch URI Scheme --> <intent-filter> diff --git a/app/src/main/java/org/openedx/app/AppActivity.kt b/app/src/main/java/org/openedx/app/AppActivity.kt index 5ab0d0b..6e5089e 100644 --- a/app/src/main/java/org/openedx/app/AppActivity.kt +++ b/app/src/main/java/org/openedx/app/AppActivity.kt @@ -3,6 +3,7 @@ package org.openedx.app import android.content.Intent import android.content.res.Configuration import android.graphics.Color +import android.net.Uri import android.os.Bundle import android.view.View import android.view.WindowManager @@ -56,6 +57,14 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder { private var _insetCutout = 0 private var _windowSize = WindowSize(WindowType.Compact, WindowType.Compact) + private val authCode: String? + get() { + val data = intent?.data + if (data is Uri && data.scheme == BuildConfig.APPLICATION_ID && data.host == "oauth2Callback") { + return data.getQueryParameter("code") + } + return null + } override fun onSaveInstanceState(outState: Bundle) { outState.putInt(TOP_INSET, topInset) @@ -119,10 +128,15 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder { if (savedInstanceState == null) { when { corePreferencesManager.user == null -> { - if (viewModel.isLogistrationEnabled) { + val authCode = authCode; + if (viewModel.isLogistrationEnabled && authCode == null) { addFragment(LogistrationFragment()) } else { - addFragment(SignInFragment()) + val bundle = Bundle() + bundle.putString("auth_code", authCode) + val fragment = SignInFragment() + fragment.arguments = bundle + addFragment(fragment) } } diff --git a/app/src/main/java/org/openedx/app/di/AppModule.kt b/app/src/main/java/org/openedx/app/di/AppModule.kt index 16a30c0..0d3feb4 100644 --- a/app/src/main/java/org/openedx/app/di/AppModule.kt +++ b/app/src/main/java/org/openedx/app/di/AppModule.kt @@ -21,6 +21,7 @@ import org.openedx.app.system.notifier.AppNotifier import org.openedx.auth.presentation.AgreementProvider import org.openedx.auth.presentation.AuthAnalytics import org.openedx.auth.presentation.AuthRouter +import org.openedx.auth.presentation.sso.BrowserAuthHelper import org.openedx.auth.presentation.sso.FacebookAuthHelper import org.openedx.auth.presentation.sso.GoogleAuthHelper import org.openedx.auth.presentation.sso.MicrosoftAuthHelper @@ -180,5 +181,6 @@ val appModule = module { factory { FacebookAuthHelper() } factory { GoogleAuthHelper(get()) } factory { MicrosoftAuthHelper() } + factory { BrowserAuthHelper(get()) } factory { OAuthHelper(get(), get(), get()) } } diff --git a/auth/build.gradle b/auth/build.gradle index 7cf4d0a..b66db95 100644 --- a/auth/build.gradle +++ b/auth/build.gradle @@ -55,6 +55,7 @@ android { dependencies { implementation project(path: ':core') + implementation 'androidx.browser:browser:1.7.0' implementation "androidx.credentials:credentials:1.2.0" implementation "androidx.credentials:credentials-play-services-auth:1.2.0" implementation "com.facebook.android:facebook-login:16.2.0" diff --git a/auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt b/auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt index 903cbd6..6d40554 100644 --- a/auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt +++ b/auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt @@ -32,6 +32,14 @@ interface AuthApi { @field("asymmetric_jwt") isAsymmetricJwt: Boolean = true, ): AuthResponse + @FormUrlEncoded + @post(ApiConstants.URL_ACCESS_TOKEN) + suspend fun getAccessTokenFromCode( + @field("grant_type") grantType: String, + @field("client_id") clientId: String, + @field("code") code: String, + ): AuthResponse + @FormUrlEncoded @post(ApiConstants.URL_ACCESS_TOKEN) fun refreshAccessToken( diff --git a/auth/src/main/java/org/openedx/auth/data/model/AuthType.kt b/auth/src/main/java/org/openedx/auth/data/model/AuthType.kt index 5addd62..c56ba0c 100644 --- a/auth/src/main/java/org/openedx/auth/data/model/AuthType.kt +++ b/auth/src/main/java/org/openedx/auth/data/model/AuthType.kt @@ -13,4 +13,5 @@ enum class AuthType(val postfix: String, val methodName: String) { GOOGLE(ApiConstants.AUTH_TYPE_GOOGLE, "Google"), FACEBOOK(ApiConstants.AUTH_TYPE_FB, "Facebook"), MICROSOFT(ApiConstants.AUTH_TYPE_MICROSOFT, "Microsoft"), + BROWSER(ApiConstants.AUTH_TYPE_BROWSER, "Browser") } diff --git a/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt b/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt index 6cf54a7..a7d364a 100644 --- a/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt +++ b/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt @@ -1,5 +1,6 @@ package org.openedx.auth.data.repository +import android.util.Log import org.openedx.auth.data.api.AuthApi import org.openedx.auth.data.model.AuthType import org.openedx.auth.data.model.ValidationFields @@ -43,6 +44,14 @@ class AuthRepository( .processAuthResponse() } + suspend fun browserAuthCodeLogin(code: String) { + api.getAccessTokenFromCode( + grantType = ApiConstants.GRANT_TYPE_CODE, + clientId = config.getOAuthClientId(), + code = code, + ).mapToDomain().processAuthResponse() + } + suspend fun getRegistrationFields(): List<RegistrationField> { return api.getRegistrationFields().fields?.map { it.mapToDomain() } ?: emptyList() } diff --git a/auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt b/auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt index 00fe509..d81c51e 100644 --- a/auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt +++ b/auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt @@ -18,6 +18,10 @@ class AuthInteractor(private val repository: AuthRepository) { repository.socialLogin(token, authType) } + suspend fun loginAuthCode(authCode: String) { + repository.browserAuthCodeLogin(authCode) + } + suspend fun getRegistrationFields(): List<RegistrationField> { return repository.getRegistrationFields() } diff --git a/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt index 738364c..0b615f3 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt @@ -1,6 +1,8 @@ package org.openedx.auth.presentation.logistration +import android.content.Intent import android.content.res.Configuration +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup @@ -41,6 +43,9 @@ import androidx.fragment.app.Fragment import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.openedx.auth.R +import org.openedx.auth.presentation.AuthRouter +import org.openedx.core.config.Config +import org.openedx.core.presentation.dialog.alert.ActionDialogFragment import org.openedx.core.ui.AuthButtonsPanel import org.openedx.core.ui.SearchBar import org.openedx.core.ui.displayCutoutForLandscape @@ -48,6 +53,7 @@ import org.openedx.core.ui.noRippleClickable import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appTypography +import org.openedx.core.utils.UrlUtils import org.openedx.core.ui.theme.compose.LogistrationLogoView class LogistrationFragment : Fragment() { @@ -55,6 +61,8 @@ class LogistrationFragment : Fragment() { private val viewModel: LogistrationViewModel by viewModel { parametersOf(arguments?.getString(ARG_COURSE_ID, "") ?: "") } + private val router: AuthRouter by inject() + private val config: Config by inject() override fun onCreateView( inflater: LayoutInflater, @@ -70,6 +78,15 @@ class LogistrationFragment : Fragment() { }, onRegisterClick = { viewModel.navigateToSignUp(parentFragmentManager) + if (config.isBrowserRegistrationEnabled()) { + UrlUtils.openInBrowser( + activity = context, + apiHostUrl = config.getApiHostURL(), + url = "/register", + ) + } else { + router.navigateToSignUp(parentFragmentManager, courseId) + } }, onSearchClick = { querySearch -> viewModel.navigateToDiscovery(parentFragmentManager, querySearch) diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt index fabd8a4..e89c000 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt @@ -1,6 +1,7 @@ package org.openedx.auth.presentation.signin import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import androidx.compose.runtime.LaunchedEffect @@ -43,6 +44,11 @@ class SignInFragment : Fragment() { val appUpgradeEvent by viewModel.appUpgradeEvent.observeAsState(null) if (appUpgradeEvent == null) { + val authCode = arguments?.getString("auth_code") + if (authCode is String && !state.loginFailure && !state.loginSuccess) { + arguments?.remove("auth_code") + viewModel.signInAuthCode(authCode) + } LoginScreen( windowSize = windowSize, state = state, @@ -59,6 +65,10 @@ class SignInFragment : Fragment() { viewModel.navigateToForgotPassword(parentFragmentManager) } + AuthEvent.SignInBrowser -> { + viewModel.signInBrowser(requireActivity()) + } + AuthEvent.RegisterClick -> { viewModel.navigateToSignUp(parentFragmentManager) } @@ -107,6 +117,7 @@ internal sealed interface AuthEvent { data class SignIn(val login: String, val password: String) : AuthEvent data class SocialSignIn(val authType: AuthType) : AuthEvent data class OpenLink(val links: Map<String, String>, val link: String) : AuthEvent + object SignInBrowser : AuthEvent object RegisterClick : AuthEvent object ForgotPasswordClick : AuthEvent object BackClick : AuthEvent diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInUIState.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInUIState.kt index 9ce5cfc..8954c1f 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInUIState.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInUIState.kt @@ -17,8 +17,11 @@ internal data class SignInUIState( val isGoogleAuthEnabled: Boolean = false, val isMicrosoftAuthEnabled: Boolean = false, val isSocialAuthEnabled: Boolean = false, + val isBrowserLoginEnabled: Boolean = false, + val isBrowserRegistrationEnabled: Boolean = false, val isLogistrationEnabled: Boolean = false, val showProgress: Boolean = false, val loginSuccess: Boolean = false, val agreement: RegistrationField? = null, + val loginFailure: Boolean = false, ) diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInViewModel.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInViewModel.kt index 7ebc5a5..4e6db6a 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInViewModel.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInViewModel.kt @@ -17,6 +17,7 @@ import org.openedx.auth.domain.interactor.AuthInteractor import org.openedx.auth.domain.model.SocialAuthResponse import org.openedx.auth.presentation.AgreementProvider import org.openedx.auth.presentation.AuthAnalytics +import org.openedx.auth.presentation.sso.BrowserAuthHelper import org.openedx.auth.presentation.AuthAnalyticsEvent import org.openedx.auth.presentation.AuthAnalyticsKey import org.openedx.auth.presentation.AuthRouter @@ -49,6 +50,11 @@ class SignInViewModel( private val whatsNewGlobalManager: WhatsNewGlobalManager, agreementProvider: AgreementProvider, config: Config, + private val facebookAuthHelper: FacebookAuthHelper, + private val googleAuthHelper: GoogleAuthHelper, + private val microsoftAuthHelper: MicrosoftAuthHelper, + private val browserAuthHelper: BrowserAuthHelper, + val config: Config, val courseId: String?, val infoType: String?, ) : BaseViewModel() { @@ -60,6 +66,8 @@ class SignInViewModel( isFacebookAuthEnabled = config.getFacebookConfig().isEnabled(), isGoogleAuthEnabled = config.getGoogleConfig().isEnabled(), isMicrosoftAuthEnabled = config.getMicrosoftConfig().isEnabled(), + isBrowserLoginEnabled = config.isBrowserLoginEnabled(), + isBrowserRegistrationEnabled = config.isBrowserRegistrationEnabled(), isSocialAuthEnabled = config.isSocialAuthEnabled(), isLogistrationEnabled = config.isPreLoginExperienceEnabled(), agreement = agreementProvider.getAgreement(isSignIn = true)?.createHonorCodeField(), @@ -144,11 +152,42 @@ class SignInViewModel( } } + fun signInBrowser(activityContext: Activity) { + _uiState.update { it.copy(showProgress = true) } + viewModelScope.launch { + runCatching { + browserAuthHelper.signIn(activityContext) + }.onFailure { + logger.e { "Browser auth error: $it" } + } + } + } + fun navigateToSignUp(parentFragmentManager: FragmentManager) { router.navigateToSignUp(parentFragmentManager, null, null) logEvent(AuthAnalyticsEvent.REGISTER_CLICKED) } + fun signInAuthCode(authCode: String) { + _uiState.update { it.copy(showProgress = true) } + viewModelScope.launch { + runCatching { + interactor.loginAuthCode(authCode) + } + .onFailure { + logger.e { "OAuth2 code error: $it" } + onUnknownError() + _uiState.update { it.copy(loginFailure = true) } + }.onSuccess { + logger.d { "Browser login success" } + _uiState.update { it.copy(loginSuccess = true) } + setUserId() + analytics.userLoginEvent(AuthType.BROWSER.methodName) + _uiState.update { it.copy(showProgress = false) } + } + } + } + fun navigateToForgotPassword(parentFragmentManager: FragmentManager) { router.navigateToRestorePassword(parentFragmentManager) logEvent(AuthAnalyticsEvent.FORGOT_PASSWORD_CLICKED) diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt index 77e2909..642ab2f 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt @@ -218,55 +218,60 @@ private fun AuthForm( var password by rememberSaveable { mutableStateOf("") } Column(horizontalAlignment = Alignment.CenterHorizontally) { - LoginTextField( - modifier = Modifier - .fillMaxWidth(), - title = stringResource(id = R.string.auth_email_username), - description = stringResource(id = R.string.auth_enter_email_username), - onValueChanged = { - login = it - }) + if (!state.isBrowserLoginEnabled) { + LoginTextField( + modifier = Modifier + .fillMaxWidth(), + title = stringResource(id = R.string.auth_email_username), + description = stringResource(id = R.string.auth_enter_email_username),onValueChanged = { + login = it + }) - Spacer(modifier = Modifier.height(18.dp)) - PasswordTextField( - modifier = Modifier - .fillMaxWidth(), - onValueChanged = { - password = it - }, - onPressDone = { - onEvent(AuthEvent.SignIn(login = login, password = password)) - } - ) + Spacer(modifier = Modifier.height(18.dp)) + PasswordTextField( + modifier = Modifier + .fillMaxWidth(), + onValueChanged = { + password = it + }, + onPressDone = { + onEvent(AuthEvent.SignIn(login = login, password = password)) + } + ) + } else { + Spacer(modifier = Modifier.height(40.dp)) + } Row( Modifier .fillMaxWidth() .padding(top = 20.dp, bottom = 36.dp) ) { - if (state.isLogistrationEnabled.not()) { - Text( - modifier = Modifier + if (!state.isBrowserLoginEnabled) { + if (state.isLogistrationEnabled.not()) { + Text( + modifier = Modifier .testTag("txt_register") .noRippleClickable { onEvent(AuthEvent.RegisterClick) }, - text = stringResource(id = coreR.string.core_register), - color = MaterialTheme.appColors.primary, - style = MaterialTheme.appTypography.labelLarge - ) - } - Spacer(modifier = Modifier.weight(1f)) - Text( - modifier = Modifier + text = stringResource(id = coreR.string.core_register), + color = MaterialTheme.appColors.primary, + style = MaterialTheme.appTypography.labelLarge + ) + } + Spacer(modifier = Modifier.weight(1f)) + Text( + modifier = Modifier .testTag("txt_forgot_password") .noRippleClickable { onEvent(AuthEvent.ForgotPasswordClick) }, - text = stringResource(id = R.string.auth_forgot_password), - color = MaterialTheme.appColors.primary, - style = MaterialTheme.appTypography.labelLarge - ) + text = stringResource(id = R.string.auth_forgot_password), + color = MaterialTheme.appColors.primary, + style = MaterialTheme.appTypography.labelLarge + ) + } } if (state.showProgress) { @@ -276,7 +281,11 @@ private fun AuthForm( modifier = buttonWidth.testTag("btn_sign_in"), text = stringResource(id = coreR.string.core_sign_in), onClick = { - onEvent(AuthEvent.SignIn(login = login, password = password)) + if(state.isBrowserLoginEnabled) { + onEvent(AuthEvent.SignInBrowser) + } else { + onEvent(AuthEvent.SignIn(login = login, password = password)) + } } ) } @@ -365,6 +374,24 @@ private fun SignInScreenPreview() { } } +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +@Preview(name = "NEXUS_5_Light", device = Devices.NEXUS_5, uiMode = UI_MODE_NIGHT_NO) +@Preview(name = "NEXUS_5_Dark", device = Devices.NEXUS_5, uiMode = UI_MODE_NIGHT_YES) +@composable +private fun SignInUsingBrowserScreenPreview() { + OpenEdXTheme { + LoginScreen( + windowSize = WindowSize(WindowType.Compact, WindowType.Compact), + state = SignInUIState().copy( + isBrowserLoginEnabled = true, + ), + uiMessage = null, + onEvent = {}, + ) + } +} + @Preview(name = "NEXUS_9_Light", device = Devices.NEXUS_9, uiMode = UI_MODE_NIGHT_NO) @Preview(name = "NEXUS_9_Night", device = Devices.NEXUS_9, uiMode = UI_MODE_NIGHT_YES) @composable diff --git a/auth/src/main/java/org/openedx/auth/presentation/sso/BrowserAuthHelper.kt b/auth/src/main/java/org/openedx/auth/presentation/sso/BrowserAuthHelper.kt new file mode 100644 index 0000000..5822bab --- /dev/null +++ b/auth/src/main/java/org/openedx/auth/presentation/sso/BrowserAuthHelper.kt @@ -0,0 +1,32 @@ +package org.openedx.auth.presentation.sso + +import android.app.Activity +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.net.Uri +import androidx.annotation.WorkerThread +import androidx.browser.customtabs.CustomTabsIntent +import org.openedx.core.config.Config +import org.openedx.core.utils.Logger + +class BrowserAuthHelper(private val config: Config) { + + private val logger = Logger(TAG) + + @workerthread + suspend fun signIn(activityContext: Activity) { + logger.d { "Browser-based auth initiated" } + val uri = Uri.parse("${config.getApiHostURL()}/oauth2/authorize").buildUpon() + .appendQueryParameter("client_id", config.getOAuthClientId()) + .appendQueryParameter("redirect_uri", "${activityContext.packageName}://oauth2Callback") + .appendQueryParameter("response_type", "code").build() + val intent = + CustomTabsIntent.Builder().setUrlBarHidingEnabled(true).setShowTitle(true).build() + intent.intent.setFlags(FLAG_ACTIVITY_NEW_TASK) + logger.d { "Launching custom tab with ${uri.toString()}"} + intent.launchUrl(activityContext, uri) + } + + private companion object { + const val TAG = "BrowserAuthHelper" + } +} diff --git a/auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt b/auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt index b36aabb..0d22d93 100644 --- a/auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt +++ b/auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt @@ -25,6 +25,7 @@ import org.openedx.auth.R import org.openedx.auth.domain.interactor.AuthInteractor import org.openedx.auth.presentation.AgreementProvider import org.openedx.auth.presentation.AuthAnalytics +import org.openedx.auth.presentation.sso.BrowserAuthHelper import org.openedx.auth.presentation.AuthRouter import org.openedx.auth.presentation.sso.OAuthHelper import org.openedx.core.UIMessage @@ -61,6 +62,7 @@ class SignInViewModelTest { private val oAuthHelper = mockk<OAuthHelper>() private val router = mockk<AuthRouter>() private val whatsNewGlobalManager = mockk<WhatsNewGlobalManager>() + private val browserAuthHelper = mockk<BrowserAuthHelper>() private val invalidCredential = "Invalid credentials" private val noInternet = "Slow or no internet connection" @@ -85,6 +87,8 @@ class SignInViewModelTest { every { config.getFacebookConfig() } returns FacebookConfig() every { config.getGoogleConfig() } returns GoogleConfig() every { config.getMicrosoftConfig() } returns MicrosoftConfig() + every { config.isBrowserLoginEnabled() } returns false + every { config.isBrowserRegistrationEnabled() } returns false } @after @@ -110,6 +114,7 @@ class SignInViewModelTest { config = config, router = router, whatsNewGlobalManager = whatsNewGlobalManager, + browserAuthHelper = browserAuthHelper, courseId = "", infoType = "", ) @@ -143,6 +148,7 @@ class SignInViewModelTest { config = config, router = router, whatsNewGlobalManager = whatsNewGlobalManager, + browserAuthHelper = browserAuthHelper, courseId = "", infoType = "", ) @@ -177,6 +183,7 @@ class SignInViewModelTest { config = config, router = router, whatsNewGlobalManager = whatsNewGlobalManager, + browserAuthHelper = browserAuthHelper, courseId = "", infoType = "", ) @@ -210,6 +217,7 @@ class SignInViewModelTest { config = config, router = router, whatsNewGlobalManager = whatsNewGlobalManager, + browserAuthHelper = browserAuthHelper, courseId = "", infoType = "", ) @@ -245,6 +253,7 @@ class SignInViewModelTest { config = config, router = router, whatsNewGlobalManager = whatsNewGlobalManager, + browserAuthHelper = browserAuthHelper, courseId = "", infoType = "", ) @@ -281,6 +290,7 @@ class SignInViewModelTest { config = config, router = router, whatsNewGlobalManager = whatsNewGlobalManager, + browserAuthHelper = browserAuthHelper, courseId = "", infoType = "", ) @@ -319,6 +329,7 @@ class SignInViewModelTest { config = config, router = router, whatsNewGlobalManager = whatsNewGlobalManager, + browserAuthHelper = browserAuthHelper, courseId = "", infoType = "", ) @@ -357,6 +368,7 @@ class SignInViewModelTest { config = config, router = router, whatsNewGlobalManager = whatsNewGlobalManager, + browserAuthHelper = browserAuthHelper, courseId = "", infoType = "", ) diff --git a/core/src/main/java/org/openedx/core/ApiConstants.kt b/core/src/main/java/org/openedx/core/ApiConstants.kt index 786d63c..86d678c 100644 --- a/core/src/main/java/org/openedx/core/ApiConstants.kt +++ b/core/src/main/java/org/openedx/core/ApiConstants.kt @@ -12,6 +12,7 @@ object ApiConstants { const val URL_PASSWORD_RESET = "/password_reset/" const val GRANT_TYPE_PASSWORD = "password" + const val GRANT_TYPE_CODE = "authorization_code" const val TOKEN_TYPE_BEARER = "Bearer" const val TOKEN_TYPE_JWT = "jwt" @@ -27,6 +28,7 @@ object ApiConstants { const val AUTH_TYPE_GOOGLE = "google-oauth2" const val AUTH_TYPE_FB = "facebook" const val AUTH_TYPE_MICROSOFT = "azuread-oauth2" + const val AUTH_TYPE_BROWSER = "browser" const val COURSE_KEY = "course_key" diff --git a/core/src/main/java/org/openedx/core/config/Config.kt b/core/src/main/java/org/openedx/core/config/Config.kt index 4b40fbc..186c6d3 100644 --- a/core/src/main/java/org/openedx/core/config/Config.kt +++ b/core/src/main/java/org/openedx/core/config/Config.kt @@ -111,6 +111,14 @@ class Config(context: Context) { return getBoolean(COURSE_UNIT_PROGRESS_ENABLED, false) } + fun isBrowserLoginEnabled(): Boolean { + return getBoolean(BROWSER_LOGIN, false) + } + + fun isBrowserRegistrationEnabled(): Boolean { + return getBoolean(BROWSER_REGISTRATION, false) + } + private fun getString(key: String, defaultValue: String): String { val element = getObject(key) return if (element != null) { @@ -162,6 +170,8 @@ class Config(context: Context) { private const val GOOGLE = "GOOGLE" private const val MICROSOFT = "MICROSOFT" private const val PRE_LOGIN_EXPERIENCE_ENABLED = "PRE_LOGIN_EXPERIENCE_ENABLED" + private const val BROWSER_LOGIN = "BROWSER_LOGIN" + private const val BROWSER_REGISTRATION = "BROWSER_REGISTRATION" private const val DISCOVERY = "DISCOVERY" private const val PROGRAM = "PROGRAM" private const val BRANCH = "BRANCH" diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml index e1582bf..9347d27 100644 --- a/default_config/dev/config.yaml +++ b/default_config/dev/config.yaml @@ -75,6 +75,10 @@ TOKEN_TYPE: "JWT" WHATS_NEW_ENABLED: false #feature flag enable Social Login buttons SOCIAL_AUTH_ENABLED: false +#feature flag to do the authentication flow in the browser to log in +BROWSER_LOGIN: false +#feature flag to do the registration for in the browser +BROWSER_REGISTRATION: false #Course navigation feature flags COURSE_NESTED_LIST_ENABLED: false COURSE_UNIT_PROGRESS_ENABLED: false diff --git a/default_config/prod/config.yaml b/default_config/prod/config.yaml index f7afc7b..fa71747 100644 --- a/default_config/prod/config.yaml +++ b/default_config/prod/config.yaml @@ -75,6 +75,10 @@ TOKEN_TYPE: "JWT" WHATS_NEW_ENABLED: false #feature flag enable Social Login buttons SOCIAL_AUTH_ENABLED: false +#feature flag to do the authentication flow in the browser to log in +BROWSER_LOGIN: false +#feature flag to do the registration for in the browser +BROWSER_REGISTRATION: false #Course navigation feature flags COURSE_NESTED_LIST_ENABLED: false COURSE_UNIT_PROGRESS_ENABLED: false diff --git a/default_config/stage/config.yaml b/default_config/stage/config.yaml index f7afc7b..fa71747 100644 --- a/default_config/stage/config.yaml +++ b/default_config/stage/config.yaml @@ -75,6 +75,10 @@ TOKEN_TYPE: "JWT" WHATS_NEW_ENABLED: false #feature flag enable Social Login buttons SOCIAL_AUTH_ENABLED: false +#feature flag to do the authentication flow in the browser to log in +BROWSER_LOGIN: false +#feature flag to do the registration for in the browser +BROWSER_REGISTRATION: false #Course navigation feature flags COURSE_NESTED_LIST_ENABLED: false COURSE_UNIT_PROGRESS_ENABLED: false
Thanks for the pull request, @xitij2000! What's next?Please work through the following steps to get your changes ready for engineering review: 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. 🔘 Let us know that your PR is ready for review:Who will review my changes?This repository is currently maintained by Where can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:
When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xitij2000 Nice work! 👍
👍
- I tested this: (tested login in both real hardware as well as emulator)
- I read through the code
- I checked for accessibility issues
- Includes documentation
This change adds support for logging in and registering a new account using the
browser. This can be useful for cases where the only way to log into the
instatance is via a custom third-party auth provider.