Commit 647b7dd3 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Check for server accounts information before showing the login options view.

This also will show the login with username/email + password with there is only this method enabled by the server.
parent e89bc7cc
...@@ -15,11 +15,13 @@ import chat.rocket.android.analytics.AnalyticsManager ...@@ -15,11 +15,13 @@ import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.login.presentation.LoginPresenter import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.helper.getCredentials import chat.rocket.android.helper.getCredentials
import chat.rocket.android.helper.hasCredentialsSupport import chat.rocket.android.helper.hasCredentialsSupport
import chat.rocket.android.helper.requestStoredCredentials import chat.rocket.android.helper.requestStoredCredentials
import chat.rocket.android.helper.saveCredentials import chat.rocket.android.helper.saveCredentials
import chat.rocket.android.util.extension.asObservable import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extensions.clearLightStatusBar
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
...@@ -27,25 +29,40 @@ import chat.rocket.android.util.extensions.ui ...@@ -27,25 +29,40 @@ import chat.rocket.android.util.extensions.ui
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_authentication_log_in.* import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import javax.inject.Inject import javax.inject.Inject
private const val SERVER_NAME = "server_name"
internal const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1 internal const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1
internal const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2 internal const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2
internal const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3 internal const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3
fun newInstance() = LoginFragment() fun newInstance(serverName: String): Fragment {
return LoginFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_NAME, serverName)
}
}
}
class LoginFragment : Fragment(), LoginView { class LoginFragment : Fragment(), LoginView {
@Inject @Inject
lateinit var presenter: LoginPresenter lateinit var presenter: LoginPresenter
@Inject @Inject
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
private var serverName: String? = null
private val editTextsDisposable = CompositeDisposable() private val editTextsDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
serverName = bundle.getString(SERVER_NAME)
}
} }
override fun onCreateView( override fun onCreateView(
...@@ -56,11 +73,10 @@ class LoginFragment : Fragment(), LoginView { ...@@ -56,11 +73,10 @@ class LoginFragment : Fragment(), LoginView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar()
presenter.setupView() presenter.setupView()
subscribeEditTexts() subscribeEditTexts()
setupOnClickListener() setupOnClickListener()
analyticsManager.logScreenView(ScreenViewEvent.Login) analyticsManager.logScreenView(ScreenViewEvent.Login)
} }
...@@ -95,6 +111,15 @@ class LoginFragment : Fragment(), LoginView { ...@@ -95,6 +111,15 @@ class LoginFragment : Fragment(), LoginView {
unsubscribeEditTexts() unsubscribeEditTexts()
} }
private fun setupToolbar() {
with(activity as AuthenticationActivity) {
this.clearLightStatusBar()
toolbar.isVisible = true
toolbar.title = serverName?.replace(getString(R.string.default_protocol), "")
}
}
override fun showLoading() { override fun showLoading() {
ui { ui {
disableUserInput() disableUserInput()
......
...@@ -5,7 +5,6 @@ import chat.rocket.android.analytics.event.AuthenticationEvent ...@@ -5,7 +5,6 @@ import chat.rocket.android.analytics.event.AuthenticationEvent
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetConnectingServerInteractor import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
...@@ -13,28 +12,12 @@ import chat.rocket.android.server.domain.PublicSettings ...@@ -13,28 +12,12 @@ import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SaveAccountInteractor import chat.rocket.android.server.domain.SaveAccountInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.casLoginUrl
import chat.rocket.android.server.domain.favicon import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.gitlabUrl
import chat.rocket.android.server.domain.isCasAuthenticationEnabled
import chat.rocket.android.server.domain.isFacebookAuthenticationEnabled
import chat.rocket.android.server.domain.isGithubAuthenticationEnabled
import chat.rocket.android.server.domain.isGitlabAuthenticationEnabled
import chat.rocket.android.server.domain.isGoogleAuthenticationEnabled
import chat.rocket.android.server.domain.isLinkedinAuthenticationEnabled
import chat.rocket.android.server.domain.isLoginFormEnabled
import chat.rocket.android.server.domain.isRegistrationEnabledForNewUsers
import chat.rocket.android.server.domain.isWordpressAuthenticationEnabled
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.domain.wordpressUrl
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.casUrl
import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.parseColor
import chat.rocket.android.util.extensions.samlUrl
import chat.rocket.android.util.extensions.serverLogoUrl import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException import chat.rocket.common.RocketChatAuthException
...@@ -48,9 +31,7 @@ import chat.rocket.core.internal.rest.loginWithCas ...@@ -48,9 +31,7 @@ import chat.rocket.core.internal.rest.loginWithCas
import chat.rocket.core.internal.rest.loginWithOauth import chat.rocket.core.internal.rest.loginWithOauth
import chat.rocket.core.internal.rest.loginWithSaml import chat.rocket.core.internal.rest.loginWithSaml
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.settingsOauth
import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.delay
import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
...@@ -58,12 +39,6 @@ private const val TYPE_LOGIN_OAUTH = 1 ...@@ -58,12 +39,6 @@ private const val TYPE_LOGIN_OAUTH = 1
private const val TYPE_LOGIN_CAS = 2 private const val TYPE_LOGIN_CAS = 2
private const val TYPE_LOGIN_SAML = 3 private const val TYPE_LOGIN_SAML = 3
private const val TYPE_LOGIN_DEEP_LINK = 4 private const val TYPE_LOGIN_DEEP_LINK = 4
private const val SERVICE_NAME_FACEBOOK = "facebook"
private const val SERVICE_NAME_GITHUB = "github"
private const val SERVICE_NAME_GOOGLE = "google"
private const val SERVICE_NAME_LINKEDIN = "linkedin"
private const val SERVICE_NAME_GILAB = "gitlab"
private const val SERVICE_NAME_WORDPRESS = "wordpress"
class LoginOptionsPresenter @Inject constructor( class LoginOptionsPresenter @Inject constructor(
private val view: LoginOptionsView, private val view: LoginOptionsView,
...@@ -88,254 +63,9 @@ class LoginOptionsPresenter @Inject constructor( ...@@ -88,254 +63,9 @@ class LoginOptionsPresenter @Inject constructor(
private lateinit var deepLinkToken: String private lateinit var deepLinkToken: String
private lateinit var loginMethod: AuthenticationEvent private lateinit var loginMethod: AuthenticationEvent
fun setupView() {
setupConnectionInfo(currentServer)
setupAccountContainerView()
setupLoginWithEmailView()
setupCreateNewAccountView()
}
private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(currentServer)
settings = settingsInteractor.get(currentServer)
}
private fun setupAccountContainerView() {
launchUI(strategy) {
try {
val services = retryIO("settingsOauth()") {
client.settingsOauth().services
}
if (services.isNotEmpty()) {
val state = OauthHelper.getState()
var totalSocialAccountsEnabled = 0
// OAuth accounts.
if (settings.isFacebookAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_FACEBOOK)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
view.setupFacebookButtonListener(
OauthHelper.getFacebookOauthUrl(
clientId,
currentServer,
state
), state
)
view.enableLoginByFacebook()
totalSocialAccountsEnabled++
}
}
}
if (settings.isGithubAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_GITHUB)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
view.setupGithubButtonListener(
OauthHelper.getGithubOauthUrl(
clientId,
state
), state
)
view.enableLoginByGithub()
totalSocialAccountsEnabled++
}
}
}
if (settings.isGoogleAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_GOOGLE)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
view.setupGoogleButtonListener(
OauthHelper.getGoogleOauthUrl(
clientId,
currentServer,
state
), state
)
view.enableLoginByGoogle()
totalSocialAccountsEnabled++
}
}
}
if (settings.isLinkedinAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_LINKEDIN)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
view.setupLinkedinButtonListener(
OauthHelper.getLinkedinOauthUrl(
clientId,
currentServer,
state
), state
)
view.enableLoginByLinkedin()
totalSocialAccountsEnabled++
}
}
}
if (settings.isGitlabAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_GILAB)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
val gitlabOauthUrl = if (settings.gitlabUrl() != null) {
OauthHelper.getGitlabOauthUrl(
host = settings.gitlabUrl(),
clientId = clientId,
serverUrl = currentServer,
state = state
)
} else {
OauthHelper.getGitlabOauthUrl(
clientId = clientId,
serverUrl = currentServer,
state = state
)
}
view.setupGitlabButtonListener(gitlabOauthUrl, state)
view.enableLoginByGitlab()
totalSocialAccountsEnabled++
}
}
}
if (settings.isWordpressAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_WORDPRESS)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
val wordpressOauthUrl =
if (settings.wordpressUrl().isNullOrEmpty()) {
OauthHelper.getWordpressComOauthUrl(
clientId,
currentServer,
state
)
} else {
OauthHelper.getWordpressCustomOauthUrl(
getCustomOauthHost(serviceMap)
?: "https://public-api.wordpress.com",
getCustomOauthAuthorizePath(serviceMap)
?: "/oauth/authorize",
clientId,
currentServer,
SERVICE_NAME_WORDPRESS,
state,
getCustomOauthScope(serviceMap) ?: "openid"
)
}
view.setupWordpressButtonListener(wordpressOauthUrl, state)
view.enableLoginByWordpress()
totalSocialAccountsEnabled++
}
}
}
// CAS account.
if (settings.isCasAuthenticationEnabled()) {
val casToken = generateRandomString(17)
view.setupCasButtonListener(
settings.casLoginUrl().casUrl(currentServer, casToken), casToken
)
view.enableLoginByCas()
totalSocialAccountsEnabled++
}
// Custom OAuth account.
getCustomOauthServices(services).let {
for (serviceMap in it) {
val serviceName = getCustomOauthServiceName(serviceMap)
val host = getCustomOauthHost(serviceMap)
val authorizePath = getCustomOauthAuthorizePath(serviceMap)
val clientId = getOauthClientId(serviceMap)
val scope = getCustomOauthScope(serviceMap)
val textColor = getServiceNameColorForCustomOauthOrSaml(serviceMap)
val buttonColor = getServiceButtonColor(serviceMap)
if (serviceName != null &&
host != null &&
authorizePath != null &&
clientId != null &&
scope != null &&
textColor != null &&
buttonColor != null
) {
val customOauthUrl = OauthHelper.getCustomOauthUrl(
host,
authorizePath,
clientId,
currentServer,
serviceName,
state,
scope
)
view.addCustomOauthButton(
customOauthUrl,
state,
serviceName,
textColor,
buttonColor
)
totalSocialAccountsEnabled++
}
}
}
// SAML account.
getSamlServices(services).let {
val samlToken = generateRandomString(17)
for (serviceMap in it) {
val provider = getSamlProvider(serviceMap)
val serviceName = getSamlServiceName(serviceMap)
val textColor = getServiceNameColorForCustomOauthOrSaml(serviceMap)
val buttonColor = getServiceButtonColor(serviceMap)
if (provider != null &&
serviceName != null &&
textColor != null &&
buttonColor != null
) {
view.addSamlButton(
currentServer.samlUrl(provider, samlToken),
samlToken,
serviceName,
textColor,
buttonColor
)
totalSocialAccountsEnabled++
}
}
}
if (totalSocialAccountsEnabled > 0) {
view.showAccountsView()
if (totalSocialAccountsEnabled > 3) {
view.setupExpandAccountsView()
}
}
}
} catch (exception: RocketChatException) {
Timber.e(exception)
}
}
}
private fun setupLoginWithEmailView() {
if (settings.isLoginFormEnabled()) {
view.showLoginWithEmailButton()
}
}
private fun setupCreateNewAccountView() {
if (settings.isRegistrationEnabledForNewUsers() && settings.isLoginFormEnabled()) {
view.showCreateNewAccountButton()
}
}
fun toCreateAccount() = navigator.toCreateAccount() fun toCreateAccount() = navigator.toCreateAccount()
fun toLoginWithEmail() = navigator.toLogin() fun toLoginWithEmail() = navigator.toLogin(currentServer)
fun authenticateWithOauth(oauthToken: String, oauthSecret: String) { fun authenticateWithOauth(oauthToken: String, oauthSecret: String) {
credentialToken = oauthToken credentialToken = oauthToken
...@@ -365,9 +95,6 @@ class LoginOptionsPresenter @Inject constructor( ...@@ -365,9 +95,6 @@ class LoginOptionsPresenter @Inject constructor(
tokenRepository.save(serverUrl, Token(deepLinkUserId, deepLinkToken)) tokenRepository.save(serverUrl, Token(deepLinkUserId, deepLinkToken))
loginMethod = AuthenticationEvent.AuthenticationWithDeeplink loginMethod = AuthenticationEvent.AuthenticationWithDeeplink
doAuthentication(TYPE_LOGIN_DEEP_LINK) doAuthentication(TYPE_LOGIN_DEEP_LINK)
} else {
// If we don't have the login credentials, just go through normal setup and user input.
setupView()
} }
} }
...@@ -438,6 +165,12 @@ class LoginOptionsPresenter @Inject constructor( ...@@ -438,6 +165,12 @@ class LoginOptionsPresenter @Inject constructor(
} }
} }
private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(currentServer)
settings = settingsInteractor.get(currentServer)
}
private suspend fun saveAccount(username: String) { private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
...@@ -451,114 +184,4 @@ class LoginOptionsPresenter @Inject constructor( ...@@ -451,114 +184,4 @@ class LoginOptionsPresenter @Inject constructor(
} }
private fun saveToken(token: Token) = tokenRepository.save(currentServer, token) private fun saveToken(token: Token) = tokenRepository.save(currentServer, token)
/** Returns an OAuth service map given a [serviceName].
*
* @param listMap The list of [Map] to get the service from.
* @param serviceName The service name to get in the [listMap]
* @return The OAuth service map or null otherwise.
*/
private fun getServiceMap(
listMap: List<Map<String, Any>>,
serviceName: String
): Map<String, Any>? = listMap.find { map -> map.containsValue(serviceName) }
/**
* Returns the OAuth client ID of a [serviceMap].
* REMARK: This function works for common OAuth providers (Google, Facebook, Github and so on)
* as well as custom OAuth.
*
* @param serviceMap The service map to get the OAuth client ID.
* @return The OAuth client ID or null otherwise.
*/
private fun getOauthClientId(serviceMap: Map<String, Any>): String? =
serviceMap["clientId"] as? String ?: serviceMap["appId"] as? String
/**
* Returns a custom OAuth service list.
*
* @return A custom OAuth service list, otherwise an empty list if there is no custom OAuth service.
*/
private fun getCustomOauthServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["custom"] == true }
/** Returns the custom OAuth service host.
*
* @param serviceMap The service map to get the custom OAuth service host.
* @return The custom OAuth service host, otherwise null.
*/
private fun getCustomOauthHost(serviceMap: Map<String, Any>): String? =
serviceMap["serverURL"] as? String
/** Returns the custom OAuth service authorize path.
*
* @param serviceMap The service map to get the custom OAuth service authorize path.
* @return The custom OAuth service authorize path, otherwise null.
*/
private fun getCustomOauthAuthorizePath(serviceMap: Map<String, Any>): String? =
serviceMap["authorizePath"] as? String
/** Returns the custom OAuth service scope.
*
* @param serviceMap The service map to get the custom OAuth service scope.
* @return The custom OAuth service scope, otherwise null.
*/
private fun getCustomOauthScope(serviceMap: Map<String, Any>): String? =
serviceMap["scope"] as? String
/** Returns the text of the custom OAuth service.
*
* @param serviceMap The service map to get the text of the custom OAuth service.
* @return The text of the custom OAuth service, otherwise null.
*/
private fun getCustomOauthServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["service"] as? String
/**
* Returns a SAML OAuth service list.
*
* @return A SAML service list, otherwise an empty list if there is no SAML OAuth service.
*/
private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["service"] == "saml" }
/**
* Returns the SAML provider.
*
* @param serviceMap The service map to provider from.
* @return The SAML provider, otherwise null.
*/
private fun getSamlProvider(serviceMap: Map<String, Any>): String? =
(serviceMap["clientConfig"] as Map<*, *>)["provider"] as? String
/**
* Returns the text of the SAML service.
*
* @param serviceMap The service map to get the text of the SAML service.
* @return The text of the SAML service, otherwise null.
*/
private fun getSamlServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["buttonLabelText"] as? String
/**
* Returns the text color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the text color from.
* @return The text color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceNameColorForCustomOauthOrSaml(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonLabelColor"] as? String)?.parseColor()
/**
* Returns the button color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the button color from.
* @return The button color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceButtonColor(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonColor"] as? String)?.parseColor()
} }
\ No newline at end of file
...@@ -28,14 +28,34 @@ import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN ...@@ -28,14 +28,34 @@ import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent
import chat.rocket.android.webview.sso.ui.INTENT_SSO_TOKEN import chat.rocket.android.webview.sso.ui.INTENT_SSO_TOKEN
import chat.rocket.android.webview.sso.ui.ssoWebViewIntent import chat.rocket.android.webview.sso.ui.ssoWebViewIntent
import chat.rocket.common.util.ifNull
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_authentication_login_options.* import kotlinx.android.synthetic.main.fragment_authentication_login_options.*
import javax.inject.Inject import javax.inject.Inject
private const val BUNDLE_SERVER_NAME = "BUNDLE_SERVER_NAME" private const val SERVER_NAME = "server_name"
private const val DEEP_LINK_INFO = "DeepLinkInfo" private const val STATE = "state"
private const val FACEBOOK_OAUTH_URL = "facebook_oauth_url"
private const val GITHUB_OAUTH_URL = "github_oauth_url"
private const val GOOGLE_OAUTH_URL = "google_oauth_url"
private const val LINKEDIN_OAUTH_URL = "linkedin_oauth_url"
private const val GITLAB_OAUTH_URL = "gitlab_oauth_url"
private const val WORDPRESS_OAUTH_URL = "wordpress_oauth_url"
private const val CAS_LOGIN_URL = "cas_login_url"
private const val CAS_TOKEN = "cas_token"
private const val CUSTOM_OAUTH_URL = "custom_oauth_url"
private const val CUSTOM_OAUTH_SERVICE_NAME = "custom_oauth_service_name"
private const val CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR = "custom_oauth_service_name_text_color"
private const val CUSTOM_OAUTH_SERVICE_BUTTON_COLOR = "custom_oauth_service_button_color"
private const val SAML_URL = "saml_url"
private const val SAML_TOKEN = "saml_token"
private const val SAML_SERVICE_NAME = "saml_service_name"
private const val SAML_SERVICE_NAME_TEXT_COLOR = "saml_service_name_text_color"
private const val SAML_SERVICE_BUTTON_COLOR = "saml_service_button_color"
private const val TOTAL_SOCIAL_ACCOUNTS = "total_social_accounts"
private const val IS_LOGIN_FORM_ENABLED = "is_login_form_enabled"
private const val IS_NEW_ACCOUNT_CREATION_ENABLED = "is_new_account_creation_enabled"
private const val DEEP_LINK_INFO = "deep-link-info"
internal const val REQUEST_CODE_FOR_OAUTH = 1 internal const val REQUEST_CODE_FOR_OAUTH = 1
internal const val REQUEST_CODE_FOR_CAS = 2 internal const val REQUEST_CODE_FOR_CAS = 2
...@@ -43,11 +63,53 @@ internal const val REQUEST_CODE_FOR_SAML = 3 ...@@ -43,11 +63,53 @@ internal const val REQUEST_CODE_FOR_SAML = 3
fun newInstance( fun newInstance(
serverName: String, serverName: String,
state: String? = null,
facebookOauthUrl: String? = null,
githubOauthUrl: String? = null,
googleOauthUrl: String? = null,
linkedinOauthUrl: String? = null,
gitlabOauthUrl: String? = null,
wordpressOauthUrl: String? = null,
casLoginUrl: String? = null,
casToken: String? = null,
customOauthUrl: String? = null,
customOauthServiceName: String? = null,
customOauthServiceNameTextColor: Int = 0,
customOauthServiceButtonColor: Int = 0,
samlUrl: String? = null,
samlToken: String? = null,
samlServiceName: String? = null,
samlServiceNameTextColor: Int = 0,
samlServiceButtonColor: Int = 0,
totalSocialAccountsEnabled: Int = 0,
isLoginFormEnabled: Boolean,
isNewAccountCreationEnabled: Boolean,
deepLinkInfo: LoginDeepLinkInfo? = null deepLinkInfo: LoginDeepLinkInfo? = null
): Fragment { ): Fragment {
return LoginOptionsFragment().apply { return LoginOptionsFragment().apply {
arguments = Bundle(2).apply { arguments = Bundle(19).apply {
putString(BUNDLE_SERVER_NAME, serverName) putString(SERVER_NAME, serverName)
putString(STATE, state)
putString(FACEBOOK_OAUTH_URL, facebookOauthUrl)
putString(GITHUB_OAUTH_URL, githubOauthUrl)
putString(GOOGLE_OAUTH_URL, googleOauthUrl)
putString(LINKEDIN_OAUTH_URL, linkedinOauthUrl)
putString(GITLAB_OAUTH_URL, gitlabOauthUrl)
putString(WORDPRESS_OAUTH_URL, wordpressOauthUrl)
putString(CAS_LOGIN_URL, casLoginUrl)
putString(CAS_TOKEN, casToken)
putString(CUSTOM_OAUTH_URL, customOauthUrl)
putString(CUSTOM_OAUTH_SERVICE_NAME, customOauthServiceName)
putInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR, customOauthServiceNameTextColor)
putInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR, customOauthServiceButtonColor)
putString(SAML_URL, samlUrl)
putString(SAML_TOKEN, samlToken)
putString(SAML_SERVICE_NAME, samlServiceName)
putInt(SAML_SERVICE_NAME_TEXT_COLOR, samlServiceNameTextColor)
putInt(SAML_SERVICE_BUTTON_COLOR, samlServiceButtonColor)
putInt(TOTAL_SOCIAL_ACCOUNTS, totalSocialAccountsEnabled)
putBoolean(IS_LOGIN_FORM_ENABLED, isLoginFormEnabled)
putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled)
putParcelable(DEEP_LINK_INFO, deepLinkInfo) putParcelable(DEEP_LINK_INFO, deepLinkInfo)
} }
} }
...@@ -58,8 +120,29 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { ...@@ -58,8 +120,29 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
lateinit var presenter: LoginOptionsPresenter lateinit var presenter: LoginOptionsPresenter
@Inject @Inject
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
private var deepLinkInfo: LoginDeepLinkInfo? = null
private var serverName: String? = null private var serverName: String? = null
private var state: String? = null
private var facebookOauthUrl: String? = null
private var githubOauthUrl: String? = null
private var googleOauthUrl: String? = null
private var linkedinOauthUrl: String? = null
private var gitlabOauthUrl: String? = null
private var wordpressOauthUrl: String? = null
private var casLoginUrl: String? = null
private var casToken: String? = null
private var customOauthUrl: String? = null
private var customOauthServiceName: String? = null
private var customOauthServiceTextColor: Int = 0
private var customOauthServiceButtonColor: Int = 0
private var samlUrl: String? = null
private var samlToken: String? = null
private var samlServiceName: String? = null
private var samlServiceTextColor: Int = 0
private var samlServiceButtonColor: Int = 0
private var totalSocialAccountsEnabled = 0
private var isLoginFormEnabled = false
private var isNewAccountCreationEnabled = false
private var deepLinkInfo: LoginDeepLinkInfo? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -67,7 +150,28 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { ...@@ -67,7 +150,28 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
val bundle = arguments val bundle = arguments
if (bundle != null) { if (bundle != null) {
serverName = bundle.getString(BUNDLE_SERVER_NAME) serverName = bundle.getString(SERVER_NAME)
state = bundle.getString(STATE)
facebookOauthUrl = bundle.getString(FACEBOOK_OAUTH_URL)
githubOauthUrl = bundle.getString(GITHUB_OAUTH_URL)
googleOauthUrl = bundle.getString(GOOGLE_OAUTH_URL)
linkedinOauthUrl = bundle.getString(LINKEDIN_OAUTH_URL)
gitlabOauthUrl = bundle.getString(GITLAB_OAUTH_URL)
wordpressOauthUrl = bundle.getString(WORDPRESS_OAUTH_URL)
casLoginUrl = bundle.getString(CAS_LOGIN_URL)
casToken = bundle.getString(CAS_TOKEN)
customOauthUrl = bundle.getString(CUSTOM_OAUTH_URL)
customOauthServiceName = bundle.getString(CUSTOM_OAUTH_SERVICE_NAME)
customOauthServiceTextColor = bundle.getInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR)
customOauthServiceButtonColor = bundle.getInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR)
samlUrl = bundle.getString(SAML_URL)
samlToken = bundle.getString(SAML_TOKEN)
samlServiceName = bundle.getString(SAML_SERVICE_NAME)
samlServiceTextColor = bundle.getInt(SAML_SERVICE_NAME_TEXT_COLOR)
samlServiceButtonColor = bundle.getInt(SAML_SERVICE_BUTTON_COLOR)
totalSocialAccountsEnabled = bundle.getInt(TOTAL_SOCIAL_ACCOUNTS)
isLoginFormEnabled = bundle.getBoolean(IS_LOGIN_FORM_ENABLED)
isNewAccountCreationEnabled = bundle.getBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED)
deepLinkInfo = bundle.getParcelable(DEEP_LINK_INFO) deepLinkInfo = bundle.getParcelable(DEEP_LINK_INFO)
} }
} }
...@@ -81,14 +185,9 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { ...@@ -81,14 +185,9 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar() setupToolbar()
presenter.setupView() setupAccounts()
analyticsManager.logScreenView(ScreenViewEvent.LoginOptions) analyticsManager.logScreenView(ScreenViewEvent.LoginOptions)
deepLinkInfo?.let { presenter.authenticateWithDeepLink(it) }
deepLinkInfo?.let {
presenter.authenticateWithDeepLink(it)
}.ifNull {
presenter.setupView()
}
} }
private fun setupToolbar() { private fun setupToolbar() {
...@@ -99,6 +198,99 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { ...@@ -99,6 +198,99 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
} }
} }
private fun setupAccounts() {
setupSocialAccounts()
setupCas()
setupCustomOauth()
setupSaml()
setupLoginWithEmailView()
setupCreateNewAccountView()
}
private fun setupSocialAccounts() {
if (facebookOauthUrl != null && state != null) {
setupFacebookButtonListener(facebookOauthUrl.toString(), state.toString())
enableLoginByFacebook()
}
if (githubOauthUrl != null && state != null) {
setupGithubButtonListener(githubOauthUrl.toString(), state.toString())
enableLoginByGithub()
}
if (googleOauthUrl != null && state != null) {
setupGoogleButtonListener(googleOauthUrl.toString(), state.toString())
enableLoginByGoogle()
}
if (linkedinOauthUrl != null && state != null) {
setupLinkedinButtonListener(linkedinOauthUrl.toString(), state.toString())
enableLoginByLinkedin()
}
if (gitlabOauthUrl != null && state != null) {
setupGitlabButtonListener(gitlabOauthUrl.toString(), state.toString())
enableLoginByGitlab()
}
if (wordpressOauthUrl != null && state != null) {
setupWordpressButtonListener(wordpressOauthUrl.toString(), state.toString())
enableLoginByWordpress()
}
if (totalSocialAccountsEnabled > 0) {
showAccountsView()
if (totalSocialAccountsEnabled > 3) {
setupExpandAccountsView()
}
}
}
private fun setupCas() {
if (casLoginUrl != null && casToken != null) {
setupCasButtonListener(casLoginUrl.toString(), casToken.toString())
enableLoginByCas()
}
}
private fun setupCustomOauth() {
if (customOauthUrl != null && state != null && customOauthServiceName != null) {
addCustomOauthButton(
customOauthUrl.toString(),
state.toString(),
customOauthServiceName.toString(),
customOauthServiceTextColor,
customOauthServiceButtonColor
)
}
}
private fun setupSaml() {
if (samlUrl != null && samlToken != null && samlServiceName != null) {
addSamlButton(
samlUrl.toString(),
samlToken.toString(),
samlServiceName.toString(),
samlServiceTextColor,
samlServiceButtonColor
)
}
}
private fun setupLoginWithEmailView() {
if (isLoginFormEnabled) {
showLoginWithEmailButton()
}
}
private fun setupCreateNewAccountView() {
if (isNewAccountCreationEnabled) {
showCreateNewAccountButton()
}
}
// OAuth Accounts. // OAuth Accounts.
override fun enableLoginByFacebook() = enableAccountButton(button_facebook) override fun enableLoginByFacebook() = enableAccountButton(button_facebook)
...@@ -170,18 +362,18 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { ...@@ -170,18 +362,18 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
} }
override fun setupExpandAccountsView() { override fun setupExpandAccountsView() {
ui { ui { _ ->
expand_more_accounts_container.isVisible = true expand_more_accounts_container.isVisible = true
var isAccountsCollapsed = true var isAccountsCollapsed = true
button_expand_collapse_accounts.setOnClickListener { view -> button_expand_collapse_accounts.setOnClickListener {
if (isAccountsCollapsed) { isAccountsCollapsed = if (isAccountsCollapsed) {
button_expand_collapse_accounts.rotateBy(180F, 400) button_expand_collapse_accounts.rotateBy(180F, 400)
expandAccountsView() expandAccountsView()
isAccountsCollapsed = false false
} else { } else {
button_expand_collapse_accounts.rotateBy(180F, 400) button_expand_collapse_accounts.rotateBy(180F, 400)
collapseAccountsView() collapseAccountsView()
isAccountsCollapsed = true true
} }
} }
} }
......
package chat.rocket.android.authentication.onboarding.presentation package chat.rocket.android.authentication.onboarding.presentation
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.behaviours.showMessage import chat.rocket.android.core.behaviours.showMessage
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetAccountsInteractor import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveConnectingServerInteractor import chat.rocket.android.server.domain.SaveConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import javax.inject.Inject import javax.inject.Inject
class OnBoardingPresenter @Inject constructor( class OnBoardingPresenter @Inject constructor(
...@@ -16,31 +20,71 @@ class OnBoardingPresenter @Inject constructor( ...@@ -16,31 +20,71 @@ class OnBoardingPresenter @Inject constructor(
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val serverInteractor: SaveConnectingServerInteractor, private val serverInteractor: SaveConnectingServerInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor, private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val getAccountsInteractor: GetAccountsInteractor private val getAccountsInteractor: GetAccountsInteractor,
) { val settingsInteractor: GetSettingsInteractor,
val factory: RocketChatClientFactory
) : CheckServerPresenter(strategy, factory, settingsInteractor) {
fun toConnectWithAServer(deepLinkInfo: LoginDeepLinkInfo?) = fun toSignInToYourServer() = navigator.toSignInToYourServer()
navigator.toConnectWithAServer(deepLinkInfo)
fun connectToCommunityServer(communityServer: String) = fun connectToCommunityServer(communityServerUrl: String) {
connectToServer(communityServer) { navigator.toLoginOptions(communityServer) } connectToServer(communityServerUrl) {
if (totalSocialAccountsEnabled == 0 && !isNewAccountCreationEnabled) {
navigator.toLogin(communityServerUrl)
} else {
navigator.toLoginOptions(
communityServerUrl,
state,
facebookOauthUrl,
githubOauthUrl,
googleOauthUrl,
linkedinOauthUrl,
gitlabOauthUrl,
wordpressOauthUrl,
casLoginUrl,
casToken,
customOauthUrl,
customOauthServiceName,
customOauthServiceNameTextColor,
customOauthServiceButtonColor,
samlUrl,
samlToken,
samlServiceName,
samlServiceNameTextColor,
samlServiceButtonColor,
totalSocialAccountsEnabled,
isLoginFormEnabled,
isNewAccountCreationEnabled
)
}
}
}
fun toCreateANewServer(createServerUrl: String) = navigator.toWebPage(createServerUrl) fun toCreateANewServer(createServerUrl: String) = navigator.toWebPage(createServerUrl)
private fun connectToServer(server: String, block: () -> Unit) { private fun connectToServer(serverUrl: String, block: () -> Unit) {
launchUI(strategy) { launchUI(strategy) {
// Check if we already have an account for this server... // Check if we already have an account for this server...
val account = getAccountsInteractor.get().firstOrNull { it.serverUrl == server } val account = getAccountsInteractor.get().firstOrNull { it.serverUrl == serverUrl }
if (account != null) { if (account != null) {
navigator.toChatList(server) navigator.toChatList(serverUrl)
return@launchUI return@launchUI
} }
view.showLoading() view.showLoading()
try { try {
refreshSettingsInteractor.refresh(server) withContext(DefaultDispatcher) {
serverInteractor.save(server) setupConnectionInfo(serverUrl)
block()
// preparing next fragment before showing it
checkEnabledAccounts(serverUrl)
checkIfLoginFormIsEnabled()
checkIfCreateNewAccountIsEnabled()
refreshSettingsInteractor.refresh(serverUrl)
serverInteractor.save(serverUrl)
block()
}
} catch (ex: Exception) { } catch (ex: Exception) {
view.showMessage(ex) view.showMessage(ex)
} finally { } finally {
......
...@@ -9,7 +9,6 @@ import androidx.fragment.app.Fragment ...@@ -9,7 +9,6 @@ import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.authentication.domain.model.getLoginDeepLinkInfo
import chat.rocket.android.authentication.onboarding.presentation.OnBoardingPresenter import chat.rocket.android.authentication.onboarding.presentation.OnBoardingPresenter
import chat.rocket.android.authentication.onboarding.presentation.OnBoardingView import chat.rocket.android.authentication.onboarding.presentation.OnBoardingView
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
...@@ -42,13 +41,12 @@ class OnBoardingFragment : Fragment(), OnBoardingView { ...@@ -42,13 +41,12 @@ class OnBoardingFragment : Fragment(), OnBoardingView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToobar() setupToolbar()
setupOnClickListener() setupOnClickListener()
analyticsManager.logScreenView(ScreenViewEvent.OnBoarding) analyticsManager.logScreenView(ScreenViewEvent.OnBoarding)
} }
private fun setupToobar() { private fun setupToolbar() {
with(activity as AuthenticationActivity) { with(activity as AuthenticationActivity) {
view?.let { this.setLightStatusBar(it) } view?.let { this.setLightStatusBar(it) }
toolbar.isVisible = false toolbar.isVisible = false
...@@ -56,7 +54,7 @@ class OnBoardingFragment : Fragment(), OnBoardingView { ...@@ -56,7 +54,7 @@ class OnBoardingFragment : Fragment(), OnBoardingView {
} }
private fun setupOnClickListener() { private fun setupOnClickListener() {
connect_with_a_server_container.setOnClickListener { connectWithAServer() } connect_with_a_server_container.setOnClickListener { signInToYourServer() }
join_community_container.setOnClickListener { joinInTheCommunity() } join_community_container.setOnClickListener { joinInTheCommunity() }
create_server_container.setOnClickListener { createANewServer() } create_server_container.setOnClickListener { createANewServer() }
} }
...@@ -87,21 +85,19 @@ class OnBoardingFragment : Fragment(), OnBoardingView { ...@@ -87,21 +85,19 @@ class OnBoardingFragment : Fragment(), OnBoardingView {
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
private fun connectWithAServer() = ui { private fun signInToYourServer() = ui {
presenter.toConnectWithAServer(activity?.intent?.getLoginDeepLinkInfo()) presenter.toSignInToYourServer()
} }
private fun joinInTheCommunity() = ui { private fun joinInTheCommunity() = ui {
presenter.connectToCommunityServer( presenter.connectToCommunityServer(
getString(R.string.default_protocol) + getString(R.string.default_protocol) + getString(R.string.community_server_url)
getString(R.string.community_server_url)
) )
} }
private fun createANewServer() = ui { private fun createANewServer() = ui {
presenter.toCreateANewServer( presenter.toCreateANewServer(
getString(R.string.default_protocol) + getString(R.string.default_protocol) + getString(R.string.create_server_url)
getString(R.string.create_server_url)
) )
} }
} }
...@@ -13,18 +13,66 @@ import chat.rocket.android.webview.ui.webViewIntent ...@@ -13,18 +13,66 @@ import chat.rocket.android.webview.ui.webViewIntent
class AuthenticationNavigator(internal val activity: AuthenticationActivity) { class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
fun toConnectWithAServer(deepLinkInfo: LoginDeepLinkInfo?) { fun toSignInToYourServer() {
activity.addFragmentBackStack(ScreenViewEvent.Server.screenName, R.id.fragment_container) { activity.addFragmentBackStack(ScreenViewEvent.Server.screenName, R.id.fragment_container) {
chat.rocket.android.authentication.server.ui.newInstance(deepLinkInfo) chat.rocket.android.authentication.server.ui.newInstance()
} }
} }
fun toLoginOptions(server: String, deepLinkInfo: LoginDeepLinkInfo? = null) { fun toLoginOptions(
serverUrl: String,
state: String? = null,
facebookOauthUrl: String? = null,
githubOauthUrl: String? = null,
googleOauthUrl: String? = null,
linkedinOauthUrl: String? = null,
gitlabOauthUrl: String? = null,
wordpressOauthUrl: String? = null,
casLoginUrl: String? = null,
casToken: String? = null,
customOauthUrl: String? = null,
customOauthServiceName: String? = null,
customOauthServiceNameTextColor: Int = 0,
customOauthServiceButtonColor: Int = 0,
samlUrl: String? = null,
samlToken: String? = null,
samlServiceName: String? = null,
samlServiceNameTextColor: Int = 0,
samlServiceButtonColor: Int = 0,
totalSocialAccountsEnabled: Int = 0,
isLoginFormEnabled: Boolean = true,
isNewAccountCreationEnabled: Boolean = true,
deepLinkInfo: LoginDeepLinkInfo? = null
) {
activity.addFragmentBackStack( activity.addFragmentBackStack(
ScreenViewEvent.LoginOptions.screenName, ScreenViewEvent.LoginOptions.screenName,
R.id.fragment_container R.id.fragment_container
) { ) {
chat.rocket.android.authentication.loginoptions.ui.newInstance(server, deepLinkInfo) chat.rocket.android.authentication.loginoptions.ui.newInstance(
serverUrl,
state,
facebookOauthUrl,
githubOauthUrl,
googleOauthUrl,
linkedinOauthUrl,
gitlabOauthUrl,
wordpressOauthUrl,
casLoginUrl,
casToken,
customOauthUrl,
customOauthServiceName,
customOauthServiceNameTextColor,
customOauthServiceButtonColor,
samlUrl,
samlToken,
samlServiceName,
samlServiceNameTextColor,
samlServiceButtonColor,
totalSocialAccountsEnabled,
isLoginFormEnabled,
isNewAccountCreationEnabled,
deepLinkInfo
)
} }
} }
...@@ -40,9 +88,9 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) { ...@@ -40,9 +88,9 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
} }
} }
fun toLogin() { fun toLogin(serverUrl: String) {
activity.addFragmentBackStack(ScreenViewEvent.Login.screenName, R.id.fragment_container) { activity.addFragmentBackStack(ScreenViewEvent.Login.screenName, R.id.fragment_container) {
chat.rocket.android.authentication.login.ui.newInstance() chat.rocket.android.authentication.login.ui.newInstance(serverUrl)
} }
} }
......
...@@ -5,6 +5,7 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator ...@@ -5,6 +5,7 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.behaviours.showMessage import chat.rocket.android.core.behaviours.showMessage
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetAccountsInteractor import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveConnectingServerInteractor import chat.rocket.android.server.domain.SaveConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
...@@ -20,20 +21,57 @@ class ServerPresenter @Inject constructor( ...@@ -20,20 +21,57 @@ class ServerPresenter @Inject constructor(
private val serverInteractor: SaveConnectingServerInteractor, private val serverInteractor: SaveConnectingServerInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor, private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
factory: RocketChatClientFactory val settingsInteractor: GetSettingsInteractor,
) : CheckServerPresenter(strategy, factory, view) { val factory: RocketChatClientFactory
) : CheckServerPresenter(strategy, factory, settingsInteractor, view) {
fun checkServer(server: String) { fun checkServer(server: String) {
if (!server.isValidUrl()) { if (!server.isValidUrl()) {
view.showInvalidServerUrlMessage() view.showInvalidServerUrlMessage()
} else { } else {
view.showLoading() view.showLoading()
setupConnectionInfo(server)
checkServerInfo(server) checkServerInfo(server)
} }
} }
fun connect(server: String) { fun connect(serverUrl: String) {
connectToServer(server) { navigator.toLoginOptions(server) } connectToServer(serverUrl) {
if (totalSocialAccountsEnabled == 0 && !isNewAccountCreationEnabled) {
navigator.toLogin(serverUrl)
} else {
navigator.toLoginOptions(
serverUrl,
state,
facebookOauthUrl,
githubOauthUrl,
googleOauthUrl,
linkedinOauthUrl,
gitlabOauthUrl,
wordpressOauthUrl,
casLoginUrl,
casToken,
customOauthUrl,
customOauthServiceName,
customOauthServiceNameTextColor,
customOauthServiceButtonColor,
samlUrl,
samlToken,
samlServiceName,
samlServiceNameTextColor,
samlServiceButtonColor,
totalSocialAccountsEnabled,
isLoginFormEnabled,
isNewAccountCreationEnabled
)
}
}
}
fun deepLink(deepLinkInfo: LoginDeepLinkInfo) {
connectToServer(deepLinkInfo.url) {
navigator.toLoginOptions(deepLinkInfo.url, deepLinkInfo = deepLinkInfo)
}
} }
private fun connectToServer(server: String, block: () -> Unit) { private fun connectToServer(server: String, block: () -> Unit) {
...@@ -47,11 +85,18 @@ class ServerPresenter @Inject constructor( ...@@ -47,11 +85,18 @@ class ServerPresenter @Inject constructor(
navigator.toChatList(server) navigator.toChatList(server)
return@launchUI return@launchUI
} }
view.showLoading() view.showLoading()
try { try {
refreshSettingsInteractor.refresh(server) refreshSettingsInteractor.refresh(server)
serverInteractor.save(server) serverInteractor.save(server)
setupConnectionInfo(server)
// preparing next fragment before showing it
checkEnabledAccounts(server)
checkIfLoginFormIsEnabled()
checkIfCreateNewAccountIsEnabled()
block() block()
} catch (ex: Exception) { } catch (ex: Exception) {
view.showMessage(ex) view.showMessage(ex)
...@@ -62,9 +107,4 @@ class ServerPresenter @Inject constructor( ...@@ -62,9 +107,4 @@ class ServerPresenter @Inject constructor(
} }
} }
fun deepLink(deepLinkInfo: LoginDeepLinkInfo) {
connectToServer(deepLinkInfo.url) {
navigator.toLoginOptions(deepLinkInfo.url, deepLinkInfo)
}
}
} }
\ No newline at end of file
...@@ -41,13 +41,7 @@ import okhttp3.HttpUrl ...@@ -41,13 +41,7 @@ import okhttp3.HttpUrl
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
fun newInstance(deepLinkInfo: LoginDeepLinkInfo?): Fragment { fun newInstance() = ServerFragment()
return ServerFragment().apply {
arguments = Bundle(1).apply {
putParcelable(DEEP_LINK_INFO, deepLinkInfo)
}
}
}
private const val DEEP_LINK_INFO = "DeepLinkInfo" private const val DEEP_LINK_INFO = "DeepLinkInfo"
......
...@@ -99,7 +99,7 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -99,7 +99,7 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
R.id.fragment_container, R.id.fragment_container,
allowStateLoss = true allowStateLoss = true
) { ) {
chat.rocket.android.authentication.server.ui.newInstance(deepLinkInfo) chat.rocket.android.authentication.server.ui.newInstance()
} }
} }
......
...@@ -3,61 +3,413 @@ package chat.rocket.android.server.presentation ...@@ -3,61 +3,413 @@ package chat.rocket.android.server.presentation
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.authentication.server.presentation.VersionCheckView import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.casLoginUrl
import chat.rocket.android.server.domain.gitlabUrl
import chat.rocket.android.server.domain.isCasAuthenticationEnabled
import chat.rocket.android.server.domain.isFacebookAuthenticationEnabled
import chat.rocket.android.server.domain.isGithubAuthenticationEnabled
import chat.rocket.android.server.domain.isGitlabAuthenticationEnabled
import chat.rocket.android.server.domain.isGoogleAuthenticationEnabled
import chat.rocket.android.server.domain.isLinkedinAuthenticationEnabled
import chat.rocket.android.server.domain.isLoginFormEnabled
import chat.rocket.android.server.domain.isRegistrationEnabledForNewUsers
import chat.rocket.android.server.domain.isWordpressAuthenticationEnabled
import chat.rocket.android.server.domain.wordpressUrl
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.VersionInfo import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.casUrl
import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.parseColor
import chat.rocket.android.util.extensions.samlUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatInvalidProtocolException import chat.rocket.common.RocketChatInvalidProtocolException
import chat.rocket.common.model.ServerInfo import chat.rocket.common.model.ServerInfo
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.serverInfo import chat.rocket.core.internal.rest.serverInfo
import chat.rocket.core.internal.rest.settingsOauth
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import timber.log.Timber import timber.log.Timber
private const val SERVICE_NAME_FACEBOOK = "facebook"
private const val SERVICE_NAME_GITHUB = "github"
private const val SERVICE_NAME_GOOGLE = "google"
private const val SERVICE_NAME_LINKEDIN = "linkedin"
private const val SERVICE_NAME_GILAB = "gitlab"
private const val SERVICE_NAME_WORDPRESS = "wordpress"
abstract class CheckServerPresenter constructor( abstract class CheckServerPresenter constructor(
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val view: VersionCheckView private val settingsInteractor: GetSettingsInteractor? = null,
private val view: VersionCheckView? = null
) { ) {
private lateinit var currentServer: String private lateinit var currentServer: String
private lateinit var client: RocketChatClient private lateinit var client: RocketChatClient
private lateinit var settings: PublicSettings
internal var state: String = ""
internal var facebookOauthUrl: String? = null
internal var githubOauthUrl: String? = null
internal var googleOauthUrl: String? = null
internal var linkedinOauthUrl: String? = null
internal var gitlabOauthUrl: String? = null
internal var wordpressOauthUrl: String? = null
internal var casLoginUrl: String? = null
internal var casToken: String? = null
internal var customOauthUrl: String? = null
internal var customOauthServiceName: String? = null
internal var customOauthServiceNameTextColor: Int = 0
internal var customOauthServiceButtonColor: Int = 0
internal var samlUrl: String? = null
internal var samlToken: String? = null
internal var samlServiceName: String? = null
internal var samlServiceNameTextColor: Int = 0
internal var samlServiceButtonColor: Int = 0
internal var totalSocialAccountsEnabled = 0
internal var isLoginFormEnabled = false
internal var isNewAccountCreationEnabled = false
internal fun setupConnectionInfo(serverUrl: String) {
settingsInteractor?.get(serverUrl)?.let {
settings = it
}
client = factory.create(serverUrl)
}
internal fun checkServerInfo(serverUrl: String): Job { internal fun checkServerInfo(serverUrl: String): Job {
return launchUI(strategy) { return launchUI(strategy) {
try { try {
currentServer = serverUrl currentServer = serverUrl
client = factory.create(currentServer)
val serverInfo = retryIO(description = "serverInfo", times = 5) { val serverInfo = retryIO(description = "serverInfo", times = 5) {
client.serverInfo() client.serverInfo()
} }
if (serverInfo.redirected) { if (serverInfo.redirected) {
view.updateServerUrl(serverInfo.url) view?.updateServerUrl(serverInfo.url)
} }
val version = checkServerVersion(serverInfo) val version = checkServerVersion(serverInfo)
when (version) { when (version) {
is Version.VersionOk -> { is Version.VersionOk -> {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: ${version.version})") Timber.i("Your version is nice! (Requires: 0.62.0, Yours: ${version.version})")
view.versionOk() view?.versionOk()
} }
is Version.RecommendedVersionWarning -> { is Version.RecommendedVersionWarning -> {
Timber.i("Your server ${version.version} is bellow recommended version ${BuildConfig.RECOMMENDED_SERVER_VERSION}") Timber.i("Your server ${version.version} is bellow recommended version ${BuildConfig.RECOMMENDED_SERVER_VERSION}")
view.alertNotRecommendedVersion() view?.alertNotRecommendedVersion()
} }
is Version.OutOfDateError -> { is Version.OutOfDateError -> {
Timber.i("Oops. Looks like your server ${version.version} is out-of-date! Minimum server version required ${BuildConfig.REQUIRED_SERVER_VERSION}!") Timber.i("Oops. Looks like your server ${version.version} is out-of-date! Minimum server version required ${BuildConfig.REQUIRED_SERVER_VERSION}!")
view.blockAndAlertNotRequiredVersion() view?.blockAndAlertNotRequiredVersion()
} }
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error getting server info") Timber.d(ex, "Error getting server info")
when (ex) { when (ex) {
is RocketChatInvalidProtocolException -> view.errorInvalidProtocol() is RocketChatInvalidProtocolException -> view?.errorInvalidProtocol()
else -> view.errorCheckingServerVersion() else -> view?.errorCheckingServerVersion()
}
}
}
}
internal suspend fun checkEnabledAccounts(serverUrl: String) {
launchUI(strategy) {
try {
val services = retryIO("settingsOauth()") {
client.settingsOauth().services
}
if (services.isNotEmpty()) {
state = OauthHelper.getState()
// OAuth accounts.
if (settings.isFacebookAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_FACEBOOK)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
facebookOauthUrl =
OauthHelper.getFacebookOauthUrl(clientId, serverUrl, state)
totalSocialAccountsEnabled++
}
}
}
if (settings.isGithubAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_GITHUB)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
githubOauthUrl =
OauthHelper.getGithubOauthUrl(clientId, state)
totalSocialAccountsEnabled++
}
}
}
if (settings.isGoogleAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_GOOGLE)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
googleOauthUrl =
OauthHelper.getGoogleOauthUrl(clientId, serverUrl, state)
totalSocialAccountsEnabled++
}
}
}
if (settings.isLinkedinAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_LINKEDIN)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
linkedinOauthUrl =
OauthHelper.getLinkedinOauthUrl(clientId, serverUrl, state)
totalSocialAccountsEnabled++
}
}
}
if (settings.isGitlabAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_GILAB)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
gitlabOauthUrl = if (settings.gitlabUrl() != null) {
OauthHelper.getGitlabOauthUrl(
host = settings.gitlabUrl(),
clientId = clientId,
serverUrl = serverUrl,
state = state
)
} else {
OauthHelper.getGitlabOauthUrl(
clientId = clientId,
serverUrl = serverUrl,
state = state
)
}
totalSocialAccountsEnabled++
}
}
}
if (settings.isWordpressAuthenticationEnabled()) {
getServiceMap(services, SERVICE_NAME_WORDPRESS)?.let { serviceMap ->
getOauthClientId(serviceMap)?.let { clientId ->
wordpressOauthUrl =
if (settings.wordpressUrl().isNullOrEmpty()) {
OauthHelper.getWordpressComOauthUrl(
clientId,
serverUrl,
state
)
} else {
OauthHelper.getWordpressCustomOauthUrl(
getCustomOauthHost(serviceMap)
?: "https://public-api.wordpress.com",
getCustomOauthAuthorizePath(serviceMap)
?: "/oauth/authorize",
clientId,
serverUrl,
SERVICE_NAME_WORDPRESS,
state,
getCustomOauthScope(serviceMap) ?: "openid"
)
}
totalSocialAccountsEnabled++
}
}
}
// CAS account.
if (settings.isCasAuthenticationEnabled()) {
casToken = generateRandomString(17)
casLoginUrl = settings.casLoginUrl().casUrl(serverUrl, casToken.toString())
totalSocialAccountsEnabled++
}
// Custom OAuth account.
getCustomOauthServices(services).let {
for (serviceMap in it) {
customOauthServiceName = getCustomOauthServiceName(serviceMap)
val host = getCustomOauthHost(serviceMap)
val authorizePath = getCustomOauthAuthorizePath(serviceMap)
val clientId = getOauthClientId(serviceMap)
val scope = getCustomOauthScope(serviceMap)
val serviceNameTextColor =
getServiceNameColorForCustomOauthOrSaml(serviceMap)
val serviceButtonColor = getServiceButtonColor(serviceMap)
if (customOauthServiceName != null &&
host != null &&
authorizePath != null &&
clientId != null &&
scope != null &&
serviceNameTextColor != null &&
serviceButtonColor != null
) {
customOauthUrl = OauthHelper.getCustomOauthUrl(
host,
authorizePath,
clientId,
serverUrl,
customOauthServiceName.toString(),
state,
scope
)
customOauthServiceNameTextColor = serviceNameTextColor
customOauthServiceButtonColor = serviceButtonColor
totalSocialAccountsEnabled++
}
}
}
// SAML account.
getSamlServices(services).let {
samlToken = generateRandomString(17)
for (serviceMap in it) {
val provider = getSamlProvider(serviceMap)
samlServiceName = getSamlServiceName(serviceMap)
val serviceNameTextColor =
getServiceNameColorForCustomOauthOrSaml(serviceMap)
val serviceButtonColor = getServiceButtonColor(serviceMap)
if (provider != null &&
samlServiceName != null &&
serviceNameTextColor != null &&
serviceButtonColor != null
) {
samlUrl = serverUrl.samlUrl(provider, samlToken.toString())
samlServiceNameTextColor = serviceNameTextColor
samlServiceButtonColor = serviceButtonColor
totalSocialAccountsEnabled++
}
}
}
} }
} catch (exception: RocketChatException) {
Timber.e(exception)
} }
} }
} }
internal fun checkIfLoginFormIsEnabled() {
if (settings.isLoginFormEnabled()) {
isLoginFormEnabled = true
}
}
internal fun checkIfCreateNewAccountIsEnabled() {
if (settings.isRegistrationEnabledForNewUsers() && settings.isLoginFormEnabled()) {
isNewAccountCreationEnabled = true
}
}
/** Returns an OAuth service map given a [serviceName].
*
* @param listMap The list of [Map] to get the service from.
* @param serviceName The service name to get in the [listMap]
* @return The OAuth service map or null otherwise.
*/
private fun getServiceMap(
listMap: List<Map<String, Any>>,
serviceName: String
): Map<String, Any>? = listMap.find { map -> map.containsValue(serviceName) }
/**
* Returns the OAuth client ID of a [serviceMap].
* REMARK: This function works for common OAuth providers (Google, Facebook, Github and so on)
* as well as custom OAuth.
*
* @param serviceMap The service map to get the OAuth client ID.
* @return The OAuth client ID or null otherwise.
*/
private fun getOauthClientId(serviceMap: Map<String, Any>): String? =
serviceMap["clientId"] as? String ?: serviceMap["appId"] as? String
/**
* Returns a custom OAuth service list.
*
* @return A custom OAuth service list, otherwise an empty list if there is no custom OAuth service.
*/
private fun getCustomOauthServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["custom"] == true }
/** Returns the custom OAuth service host.
*
* @param serviceMap The service map to get the custom OAuth service host.
* @return The custom OAuth service host, otherwise null.
*/
private fun getCustomOauthHost(serviceMap: Map<String, Any>): String? =
serviceMap["serverURL"] as? String
/** Returns the custom OAuth service authorize path.
*
* @param serviceMap The service map to get the custom OAuth service authorize path.
* @return The custom OAuth service authorize path, otherwise null.
*/
private fun getCustomOauthAuthorizePath(serviceMap: Map<String, Any>): String? =
serviceMap["authorizePath"] as? String
/** Returns the custom OAuth service scope.
*
* @param serviceMap The service map to get the custom OAuth service scope.
* @return The custom OAuth service scope, otherwise null.
*/
private fun getCustomOauthScope(serviceMap: Map<String, Any>): String? =
serviceMap["scope"] as? String
/** Returns the text of the custom OAuth service.
*
* @param serviceMap The service map to get the text of the custom OAuth service.
* @return The text of the custom OAuth service, otherwise null.
*/
private fun getCustomOauthServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["service"] as? String
/**
* Returns a SAML OAuth service list.
*
* @return A SAML service list, otherwise an empty list if there is no SAML OAuth service.
*/
private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> =
listMap.filter { map -> map["service"] == "saml" }
/**
* Returns the SAML provider.
*
* @param serviceMap The service map to provider from.
* @return The SAML provider, otherwise null.
*/
private fun getSamlProvider(serviceMap: Map<String, Any>): String? =
(serviceMap["clientConfig"] as Map<*, *>)["provider"] as? String
/**
* Returns the text of the SAML service.
*
* @param serviceMap The service map to get the text of the SAML service.
* @return The text of the SAML service, otherwise null.
*/
private fun getSamlServiceName(serviceMap: Map<String, Any>): String? =
serviceMap["buttonLabelText"] as? String
/**
* Returns the text color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the text color from.
* @return The text color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceNameColorForCustomOauthOrSaml(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonLabelColor"] as? String)?.parseColor()
/**
* Returns the button color of the service name.
* REMARK: This can be used for custom OAuth or SAML.
*
* @param serviceMap The service map to get the button color from.
* @return The button color of the service (custom OAuth or SAML), otherwise null.
*/
private fun getServiceButtonColor(serviceMap: Map<String, Any>): Int? =
(serviceMap["buttonColor"] as? String)?.parseColor()
private fun checkServerVersion(serverInfo: ServerInfo): Version { private fun checkServerVersion(serverInfo: ServerInfo): Version {
val thisServerVersion = serverInfo.version val thisServerVersion = serverInfo.version
val isRequiredVersion = isRequiredServerVersion(thisServerVersion) val isRequiredVersion = isRequiredServerVersion(thisServerVersion)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment