Unverified Commit 07ac0274 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito Committed by GitHub

Merge branch 'develop-2.x' into lang-hindi

parents f136cf38 f9d6c822
...@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME ...@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
...@@ -54,6 +54,11 @@ ...@@ -54,6 +54,11 @@
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity
android:name=".webview.oauth.ui.OauthWebViewActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:theme="@style/AppTheme" />
<activity <activity
android:name=".chatroom.ui.ChatRoomActivity" android:name=".chatroom.ui.ChatRoomActivity"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
......
package chat.rocket.android.authentication.login.presentation package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.domain.model.TokenModel import chat.rocket.android.authentication.domain.model.TokenModel
import chat.rocket.android.authentication.domain.model.toToken
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.NetworkHelper import chat.rocket.android.helper.NetworkHelper
...@@ -10,43 +9,85 @@ import chat.rocket.android.infrastructure.LocalRepository ...@@ -10,43 +9,85 @@ import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.encodeToBase64
import chat.rocket.android.util.extensions.generateRandomString import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.isEmailValid import chat.rocket.android.util.extensions.isEmail
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.extensions.registerPushToken import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException import chat.rocket.common.model.Token
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.* import chat.rocket.core.internal.rest.*
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
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
private const val TYPE_LOGIN_USER_EMAIL = 0
private const val TYPE_LOGIN_CAS = 1
private const val TYPE_LOGIN_OAUTH = 2
private const val SERVICE_NAME_GITHUB = "github"
private const val SERVICE_NAME_GOOGLE = "google"
private const val SERVICE_NAME_GILAB = "gitlab"
class LoginPresenter @Inject constructor(private val view: LoginView, class LoginPresenter @Inject constructor(private val view: LoginView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
private val settingsInteractor: GetSettingsInteractor, settingsInteractor: GetSettingsInteractor,
private val serverInteractor: GetCurrentServerInteractor, serverInteractor: GetCurrentServerInteractor,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val factory: RocketChatClientFactory) { private val factory: RocketChatClientFactory) {
// TODO - we should validate the current server when opening the app, and have a nonnull get() // TODO - we should validate the current server when opening the app, and have a nonnull get()
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) private val settings: PublicSettings = settingsInteractor.get(currentServer)
private lateinit var usernameOrEmail: String
private lateinit var password: String
private lateinit var credentialToken: String
private lateinit var credentialSecret: String
fun setupView() { fun setupView() {
val server = serverInteractor.get() setupLoginView()
if (server == null) { setupUserRegistrationView()
navigator.toServerScreen() setupCasView()
return setupOauthServicesView()
}
fun authenticateWithUserAndPassword(usernameOrEmail: String, password: String) {
when {
usernameOrEmail.isBlank() -> {
view.alertWrongUsernameOrEmail()
}
password.isEmpty() -> {
view.alertWrongPassword()
}
else -> {
this.usernameOrEmail = usernameOrEmail
this.password = password
doAuthentication(TYPE_LOGIN_USER_EMAIL)
}
} }
val settings = settingsInteractor.get(server) }
fun authenticateWithCas(token: String) {
credentialToken = token
doAuthentication(TYPE_LOGIN_CAS)
}
fun authenticateWithOauth(token: String, secret: String) {
credentialToken = token
credentialSecret = secret
doAuthentication(TYPE_LOGIN_OAUTH)
}
fun signup() = navigator.toSignUp()
private fun setupLoginView() {
if (settings.isLoginFormEnabled()) { if (settings.isLoginFormEnabled()) {
view.showFormView() view.showFormView()
view.setupLoginButtonListener() view.setupLoginButtonListener()
...@@ -54,133 +95,126 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -54,133 +95,126 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
} else { } else {
view.hideFormView() view.hideFormView()
} }
}
if (settings.isRegistrationEnabledForNewUsers()) { private fun setupCasView() {
view.showSignUpView()
view.setupSignUpView()
}
if (settings.isCasAuthenticationEnabled()) { if (settings.isCasAuthenticationEnabled()) {
val token = generateRandomString(17) val token = generateRandomString(17)
view.setupCasButtonListener(UrlHelper.getCasUrl(settings.casLoginUrl(), server, token), token) view.setupCasButtonListener(UrlHelper.getCasUrl(settings.casLoginUrl(), currentServer, token), token)
view.showCasButton() view.showCasButton()
} }
}
var totalSocialAccountsEnabled = 0 private fun setupUserRegistrationView() {
if (settings.isFacebookAuthenticationEnabled()) { if (settings.isRegistrationEnabledForNewUsers()) {
view.enableLoginByFacebook() view.showSignUpView()
totalSocialAccountsEnabled++ view.setupSignUpView()
}
if (settings.isGithubAuthenticationEnabled()) {
view.enableLoginByGithub()
totalSocialAccountsEnabled++
}
if (settings.isGoogleAuthenticationEnabled()) {
view.enableLoginByGoogle()
totalSocialAccountsEnabled++
}
if (settings.isLinkedinAuthenticationEnabled()) {
view.enableLoginByLinkedin()
totalSocialAccountsEnabled++
}
if (settings.isMeteorAuthenticationEnabled()) {
view.enableLoginByMeteor()
totalSocialAccountsEnabled++
}
if (settings.isTwitterAuthenticationEnabled()) {
view.enableLoginByTwitter()
totalSocialAccountsEnabled++
}
if (settings.isGitlabAuthenticationEnabled()) {
view.enableLoginByGitlab()
totalSocialAccountsEnabled++
}
if (totalSocialAccountsEnabled > 0) {
view.showOauthView()
if (totalSocialAccountsEnabled > 3) {
view.setupFabListener()
}
} }
} }
fun authenticate(usernameOrEmail: String, password: String) { private fun setupOauthServicesView() {
val server = serverInteractor.get() launchUI(strategy) {
when { try {
server == null -> { val services = client.settingsOauth().services
navigator.toServerScreen() val state = "{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
}
usernameOrEmail.isBlank() -> {
view.alertWrongUsernameOrEmail()
}
password.isEmpty() -> {
view.alertWrongPassword()
}
else -> {
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.disableUserInput()
view.showLoading()
try {
val token = if (usernameOrEmail.isEmailValid()) {
client.loginWithEmail(usernameOrEmail, password)
} else {
val settings = settingsInteractor.get(server)
if (settings.isLdapAuthenticationEnabled()) {
client.loginWithLdap(usernameOrEmail, password)
} else {
client.login(usernameOrEmail, password)
}
}
val me = client.me() if (services.isNotEmpty()) {
saveToken(server, TokenModel(token.userId, token.authToken), me.username) var totalSocialAccountsEnabled = 0
saveAccount(me)
registerPushToken() if (settings.isFacebookAuthenticationEnabled()) {
navigator.toChatList() view.enableLoginByFacebook()
} catch (exception: RocketChatException) { totalSocialAccountsEnabled++
when (exception) { }
is RocketChatTwoFactorException -> { if (settings.isGithubAuthenticationEnabled()) {
navigator.toTwoFA(usernameOrEmail, password) val clientId = getOauthClientId(services, SERVICE_NAME_GITHUB)
} if (clientId != null) {
else -> { view.setupGithubButtonListener(UrlHelper.getGithubOauthUrl(clientId, state), state)
exception.message?.let { view.enableLoginByGithub()
view.showMessage(it) totalSocialAccountsEnabled++
}.ifNull { }
view.showGenericErrorMessage() }
} if (settings.isGoogleAuthenticationEnabled()) {
} val clientId = getOauthClientId(services, SERVICE_NAME_GOOGLE)
} if (clientId != null) {
} finally { view.setupGoogleButtonListener(UrlHelper.getGoogleOauthUrl(clientId, currentServer, state), state)
view.hideLoading() view.enableLoginByGoogle()
view.enableUserInput() totalSocialAccountsEnabled++
}
}
if (settings.isLinkedinAuthenticationEnabled()) {
view.enableLoginByLinkedin()
totalSocialAccountsEnabled++
}
if (settings.isMeteorAuthenticationEnabled()) {
view.enableLoginByMeteor()
totalSocialAccountsEnabled++
}
if (settings.isTwitterAuthenticationEnabled()) {
view.enableLoginByTwitter()
totalSocialAccountsEnabled++
}
if (settings.isGitlabAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GILAB)
if (clientId != null) {
view.setupGitlabButtonListener(UrlHelper.getGitlabOauthUrl(clientId, currentServer, state), state)
view.enableLoginByGitlab()
totalSocialAccountsEnabled++
}
}
if (totalSocialAccountsEnabled > 0) {
view.showOauthView()
if (totalSocialAccountsEnabled > 3) {
view.setupFabListener()
} }
} else { } else {
view.showNoInternetConnection() view.hideOauthView()
} }
} else {
view.hideOauthView()
} }
} catch (exception: RocketChatException) {
Timber.e(exception)
view.hideOauthView()
} }
} }
} }
fun authenticateWithCas(casToken: String) { private fun doAuthentication(loginType: Int) {
launchUI(strategy) { launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) { if (NetworkHelper.hasInternetAccess()) {
view.disableUserInput() view.disableUserInput()
view.showLoading() view.showLoading()
try { try {
val server = serverInteractor.get() val token = when (loginType) {
if (server != null) { TYPE_LOGIN_USER_EMAIL -> {
delay(3, TimeUnit.SECONDS) if (usernameOrEmail.isEmail()) {
val token = client.loginWithCas(casToken) client.loginWithEmail(usernameOrEmail, password)
val me = client.me() } else {
saveToken(server, TokenModel(token.userId, token.authToken), me.username) if (settings.isLdapAuthenticationEnabled()) {
saveAccount(me) client.loginWithLdap(usernameOrEmail, password)
registerPushToken() } else {
navigator.toChatList() client.login(usernameOrEmail, password)
} else { }
navigator.toServerScreen() }
}
TYPE_LOGIN_CAS -> {
delay(3, TimeUnit.SECONDS)
client.loginWithCas(credentialToken)
}
TYPE_LOGIN_OAUTH -> {
client.loginWithOauth(credentialToken, credentialSecret)
}
else -> {
throw IllegalStateException("Expected TYPE_LOGIN_USER_EMAIL, TYPE_LOGIN_CAS or TYPE_LOGIN_OAUTH")
}
} }
val me = client.me()
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
saveAccount(me)
saveToken(token)
registerPushToken()
navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
...@@ -197,12 +231,8 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -197,12 +231,8 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
} }
} }
fun signup() = navigator.toSignUp() private fun saveToken(token: Token) {
tokenRepository.save(currentServer, token)
private suspend fun saveToken(server: String, tokenModel: TokenModel, username: String?) {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
tokenRepository.save(server, tokenModel.toToken())
registerPushToken()
} }
private suspend fun registerPushToken() { private suspend fun registerPushToken() {
...@@ -213,6 +243,11 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -213,6 +243,11 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
// onTokenRefresh() on FirebaseTokenService, we need to confirm it. // onTokenRefresh() on FirebaseTokenService, we need to confirm it.
} }
private fun getOauthClientId(listMap: List<Map<String, String>>, serviceName: String): String? {
return listMap.find { map -> map.containsValue(serviceName) }
?.get("appId")
}
private suspend fun saveAccount(me: Myself) { private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
UrlHelper.getServerLogoUrl(currentServer, it) UrlHelper.getServerLogoUrl(currentServer, it)
......
...@@ -37,7 +37,7 @@ interface LoginView : LoadingView, MessageView, InternetView { ...@@ -37,7 +37,7 @@ interface LoginView : LoadingView, MessageView, InternetView {
/** /**
* Shows the CAS button if the sign in/sign out via CAS protocol is enabled by the server settings. * Shows the CAS button if the sign in/sign out via CAS protocol is enabled by the server settings.
* *
* REMARK: We must set up the CAS button listener [setupCasButtonListener]. * REMARK: We must set up the CAS button listener before showing it [setupCasButtonListener].
*/ */
fun showCasButton() fun showCasButton()
...@@ -49,8 +49,8 @@ interface LoginView : LoadingView, MessageView, InternetView { ...@@ -49,8 +49,8 @@ interface LoginView : LoadingView, MessageView, InternetView {
/** /**
* Setups the CAS button when tapped. * Setups the CAS button when tapped.
* *
* @param casUrl The CAS URL to login/sign up with. * @param casUrl The CAS URL to authenticate with.
* @param casToken The requested Token sent to the CAS server. * @param casToken The requested token to be sent to the CAS server.
*/ */
fun setupCasButtonListener(casUrl: String, casToken: String) fun setupCasButtonListener(casUrl: String, casToken: String)
...@@ -96,40 +96,70 @@ interface LoginView : LoadingView, MessageView, InternetView { ...@@ -96,40 +96,70 @@ interface LoginView : LoadingView, MessageView, InternetView {
fun hideLoginButton() fun hideLoginButton()
/** /**
* Shows the "login by Facebook view if it is enabled by the server settings. * Shows the "login by Facebook view if it is enable by the server settings.
*/ */
fun enableLoginByFacebook() fun enableLoginByFacebook()
/** /**
* Shows the "login by Github" view if it is enabled by the server settings. * Shows the "login by Github" view if it is enable by the server settings.
*
* REMARK: We must set up the Github button listener before enabling it [setupGithubButtonListener].
*/ */
fun enableLoginByGithub() fun enableLoginByGithub()
/** /**
* Shows the "login by Google" view if it is enabled by the server settings. * Setups the Github button when tapped.
*
* @param githubUrl The Github OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
*/
fun setupGithubButtonListener(githubUrl: String, state: String)
/**
* Shows the "login by Google" view if it is enable by the server settings.
*
* REMARK: We must set up the Google button listener before enabling it [setupGoogleButtonListener].
*/ */
fun enableLoginByGoogle() fun enableLoginByGoogle()
/** /**
* Shows the "login by Linkedin" view if it is enabled by the server settings. * Setups the Google button when tapped.
*
* @param googleUrl The Google OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
*/
fun setupGoogleButtonListener(googleUrl: String, state: String)
/**
* Shows the "login by Linkedin" view if it is enable by the server settings.
*/ */
fun enableLoginByLinkedin() fun enableLoginByLinkedin()
/** /**
* Shows the "login by Meteor" view if it is enabled by the server settings. * Shows the "login by Meteor" view if it is enable by the server settings.
*/ */
fun enableLoginByMeteor() fun enableLoginByMeteor()
/** /**
* Shows the "login by Twitter" view if it is enabled by the server settings. * Shows the "login by Twitter" view if it is enable by the server settings.
*/ */
fun enableLoginByTwitter() fun enableLoginByTwitter()
/** /**
* Shows the "login by Gitlab" view if it is enabled by the server settings. * Shows the "login by Gitlab" view if it is enable by the server settings.
*
* REMARK: We must set up the Gitlab button listener before enabling it [setupGitlabButtonListener].
*/ */
fun enableLoginByGitlab() fun enableLoginByGitlab()
/**
* Setups the Gitlab button when tapped.
*
* @param gitlabUrl The Gitlab OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
*/
fun setupGitlabButtonListener(gitlabUrl: String, state: String)
/** /**
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)). * Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)).
*/ */
......
...@@ -19,12 +19,17 @@ import chat.rocket.android.authentication.login.presentation.LoginView ...@@ -19,12 +19,17 @@ import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import chat.rocket.android.webview.cas.ui.webViewIntent import chat.rocket.android.webview.cas.ui.INTENT_CAS_TOKEN
import chat.rocket.android.webview.cas.ui.casWebViewIntent
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_SECRET
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
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
internal const val REQUEST_CODE_FOR_CAS = 1 internal const val REQUEST_CODE_FOR_CAS = 1
internal const val REQUEST_CODE_FOR_OAUTH = 2
class LoginFragment : Fragment(), LoginView { class LoginFragment : Fragment(), LoginView {
@Inject lateinit var presenter: LoginPresenter @Inject lateinit var presenter: LoginPresenter
...@@ -64,10 +69,14 @@ class LoginFragment : Fragment(), LoginView { ...@@ -64,10 +69,14 @@ class LoginFragment : Fragment(), LoginView {
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_FOR_CAS) { if (resultCode == Activity.RESULT_OK) {
if (resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_CODE_FOR_CAS) {
data?.apply { data?.apply {
presenter.authenticateWithCas(getStringExtra("cas_token")) presenter.authenticateWithCas(getStringExtra(INTENT_CAS_TOKEN))
}
} else if (requestCode == REQUEST_CODE_FOR_OAUTH) {
data?.apply {
presenter.authenticateWithOauth(getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN), getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET))
} }
} }
} }
...@@ -121,7 +130,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -121,7 +130,7 @@ class LoginFragment : Fragment(), LoginView {
override fun setupLoginButtonListener() { override fun setupLoginButtonListener() {
button_log_in.setOnClickListener { button_log_in.setOnClickListener {
presenter.authenticate(text_username_or_email.textContent, text_password.textContent) presenter.authenticateWithUserAndPassword(text_username_or_email.textContent, text_password.textContent)
} }
} }
...@@ -147,7 +156,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -147,7 +156,7 @@ class LoginFragment : Fragment(), LoginView {
override fun setupCasButtonListener(casUrl: String, casToken: String) { override fun setupCasButtonListener(casUrl: String, casToken: String) {
button_cas.setOnClickListener { button_cas.setOnClickListener {
startActivityForResult(context?.webViewIntent(casUrl, casToken), REQUEST_CODE_FOR_CAS) startActivityForResult(context?.casWebViewIntent(casUrl, casToken), REQUEST_CODE_FOR_CAS)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold) activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
} }
} }
...@@ -192,31 +201,53 @@ class LoginFragment : Fragment(), LoginView { ...@@ -192,31 +201,53 @@ class LoginFragment : Fragment(), LoginView {
} }
override fun enableLoginByFacebook() { override fun enableLoginByFacebook() {
button_facebook.isEnabled = true button_facebook.isClickable = true
} }
override fun enableLoginByGithub() { override fun enableLoginByGithub() {
button_github.isEnabled = true button_github.isClickable = true
}
override fun setupGithubButtonListener(githubUrl: String, state: String) {
button_github.setOnClickListener {
startActivityForResult(context?.oauthWebViewIntent(githubUrl, state), REQUEST_CODE_FOR_OAUTH)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
} }
override fun enableLoginByGoogle() { override fun enableLoginByGoogle() {
button_google.isEnabled = true button_google.isClickable = true
}
// TODO: Use custom tabs instead of web view. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968
override fun setupGoogleButtonListener(googleUrl: String, state: String) {
button_google.setOnClickListener {
startActivityForResult(context?.oauthWebViewIntent(googleUrl, state), REQUEST_CODE_FOR_OAUTH)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
} }
override fun enableLoginByLinkedin() { override fun enableLoginByLinkedin() {
button_linkedin.isEnabled = true button_linkedin.isClickable = true
} }
override fun enableLoginByMeteor() { override fun enableLoginByMeteor() {
button_meteor.isEnabled = true button_meteor.isClickable = true
} }
override fun enableLoginByTwitter() { override fun enableLoginByTwitter() {
button_twitter.isEnabled = true button_twitter.isClickable = true
} }
override fun enableLoginByGitlab() { override fun enableLoginByGitlab() {
button_gitlab.isEnabled = true button_gitlab.isClickable = true
}
override fun setupGitlabButtonListener(gitlabUrl: String, state: String) {
button_gitlab.setOnClickListener {
startActivityForResult(context?.oauthWebViewIntent(gitlabUrl, state), REQUEST_CODE_FOR_OAUTH)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
} }
override fun setupFabListener() { override fun setupFabListener() {
...@@ -253,8 +284,8 @@ class LoginFragment : Fragment(), LoginView { ...@@ -253,8 +284,8 @@ class LoginFragment : Fragment(), LoginView {
social_accounts_container.postDelayed({ social_accounts_container.postDelayed({
(0..social_accounts_container.childCount) (0..social_accounts_container.childCount)
.mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton } .mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton }
.filter { it.isEnabled } .filter { it.isClickable }
.forEach { it.visibility = View.VISIBLE } .forEach { it.setVisible(true)}
}, 1000) }, 1000)
} }
...@@ -284,13 +315,10 @@ class LoginFragment : Fragment(), LoginView { ...@@ -284,13 +315,10 @@ class LoginFragment : Fragment(), LoginView {
} }
private fun showThreeSocialAccountsMethods() { private fun showThreeSocialAccountsMethods() {
var count = 0 (0..social_accounts_container.childCount)
for (i in 0..social_accounts_container.childCount) { .mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton }
val view = social_accounts_container.getChildAt(i) as? ImageButton ?: continue .filter { it.isClickable }
if (view.isEnabled && count < 3) { .take(3)
view.visibility = View.VISIBLE .forEach { it.setVisible(true) }
count++
}
}
} }
} }
\ No newline at end of file
...@@ -10,6 +10,7 @@ import chat.rocket.android.server.domain.* ...@@ -10,6 +10,7 @@ import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
...@@ -27,6 +28,7 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -27,6 +28,7 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor,
settingsInteractor: GetSettingsInteractor) { settingsInteractor: GetSettingsInteractor) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
...@@ -98,9 +100,10 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -98,9 +100,10 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
private suspend fun registerPushToken() { private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let { localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it) client.registerPushToken(it, getAccountsInteractor.get(), factory)
} }
// TODO: Schedule push token registering when it comes up null // TODO: When the push token is null, at some point we should receive it with
// onTokenRefresh() on FirebaseTokenService, we need to confirm it.
} }
private suspend fun saveAccount(me: Myself) { private suspend fun saveAccount(me: Myself) {
......
...@@ -6,5 +6,5 @@ const val PEOPLE = 0 ...@@ -6,5 +6,5 @@ const val PEOPLE = 0
const val ROOMS = 1 const val ROOMS = 1
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@IntDef(value = [PEOPLE, ROOMS]) @IntDef(PEOPLE, ROOMS)
annotation class AutoCompleteType annotation class AutoCompleteType
...@@ -53,7 +53,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -53,7 +53,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val manager = factory.create(currentServer) private val manager = factory.create(currentServer)
private val client = manager.client private val client = manager.client
private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)!! private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)
private val messagesChannel = Channel<Message>() private val messagesChannel = Channel<Message>()
private var chatRoomId: String? = null private var chatRoomId: String? = null
......
...@@ -24,7 +24,7 @@ class PinnedMessagesPresenter @Inject constructor(private val view: PinnedMessag ...@@ -24,7 +24,7 @@ class PinnedMessagesPresenter @Inject constructor(private val view: PinnedMessag
getSettingsInteractor: GetSettingsInteractor) { getSettingsInteractor: GetSettingsInteractor) {
private val client = factory.create(serverInteractor.get()!!) private val client = factory.create(serverInteractor.get()!!)
private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)!! private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)
private var pinnedMessagesListOffset: Int = 0 private var pinnedMessagesListOffset: Int = 0
/** /**
......
...@@ -36,7 +36,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -36,7 +36,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client = manager.client private val client = manager.client
private var reloadJob: Deferred<List<ChatRoom>>? = null private var reloadJob: Deferred<List<ChatRoom>>? = null
private val settings = settingsRepository.get(currentServer)!! private val settings = settingsRepository.get(currentServer)
private val subscriptionsChannel = Channel<StreamMessage<BaseRoom>>() private val subscriptionsChannel = Channel<StreamMessage<BaseRoom>>()
private val stateChannel = Channel<State>() private val stateChannel = Channel<State>()
...@@ -101,21 +101,52 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -101,21 +101,52 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private suspend fun usersToChatRooms(users: List<User>): List<ChatRoom> { private suspend fun usersToChatRooms(users: List<User>): List<ChatRoom> {
return users.map { return users.map {
ChatRoom(it.id, RoomType.DIRECT_MESSAGE, SimpleUser( ChatRoom(id = it.id,
username = it.username, name = it.name, id = null), it.name ?: "", type = RoomType.DIRECT_MESSAGE,
it.name, false, null, null, null, user = SimpleUser(username = it.username, name = it.name, id = null),
null, null, false, false, false, name = it.name ?: "",
0L, null, 0L, null, client fullName = it.name,
readonly = false,
updatedAt = null,
timestamp = null,
lastSeen = null,
topic = null,
description = null,
announcement = null,
default = false,
open = false,
alert = false,
unread = 0L,
userMenstions = null,
groupMentions = 0L,
lastMessage = null,
client = client
) )
} }
} }
private suspend fun roomsToChatRooms(rooms: List<Room>): List<ChatRoom> { private suspend fun roomsToChatRooms(rooms: List<Room>): List<ChatRoom> {
return rooms.map { return rooms.map {
ChatRoom(it.id, it.type, it.user, it.name ?: "", ChatRoom(id = it.id,
it.fullName, it.readonly, it.updatedAt, null, null, type = it.type,
it.topic, it.announcement, false, false, false, user = it.user,
0L, null, 0L, it.lastMessage, client name = it.name ?: "",
fullName = it.fullName,
readonly = it.readonly,
updatedAt = it.updatedAt,
timestamp = null,
lastSeen = null,
topic = it.topic,
description = it.description,
announcement = it.announcement,
default = false,
open = false,
alert = false,
unread = 0L,
userMenstions = null,
groupMentions = 0L,
lastMessage = it.lastMessage,
client = client
) )
} }
} }
...@@ -255,6 +286,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -255,6 +286,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
timestamp, timestamp,
lastSeen, lastSeen,
room.topic, room.topic,
room.description,
room.announcement, room.announcement,
default, default,
open, open,
...@@ -286,6 +318,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -286,6 +318,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
subscription.timestamp ?: timestamp, subscription.timestamp ?: timestamp,
subscription.lastSeen ?: lastSeen, subscription.lastSeen ?: lastSeen,
topic, topic,
description,
announcement, announcement,
subscription.isDefault, subscription.isDefault,
subscription.open, subscription.open,
......
...@@ -157,7 +157,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -157,7 +157,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
recycler_view.itemAnimator = DefaultItemAnimator() recycler_view.itemAnimator = DefaultItemAnimator()
// TODO - use a ViewModel Mapper instead of using settings on the adapter // TODO - use a ViewModel Mapper instead of using settings on the adapter
recycler_view.adapter = ChatRoomsAdapter(this, recycler_view.adapter = ChatRoomsAdapter(this,
settingsRepository.get(serverInteractor.get()!!)!!) { chatRoom -> settingsRepository.get(serverInteractor.get()!!)) { chatRoom ->
presenter.loadChatRoom(chatRoom) presenter.loadChatRoom(chatRoom)
} }
} }
......
...@@ -35,6 +35,38 @@ object UrlHelper { ...@@ -35,6 +35,38 @@ object UrlHelper {
fun getCasUrl(casLoginUrl: String, serverUrl: String, token: String): String = fun getCasUrl(casLoginUrl: String, serverUrl: String, token: String): String =
removeTrailingSlash(casLoginUrl) + "?service=" + removeTrailingSlash(serverUrl) + "/_cas/" + token removeTrailingSlash(casLoginUrl) + "?service=" + removeTrailingSlash(serverUrl) + "/_cas/" + token
/**
* Returns the Github Oauth URL.
*
* @param clientId The GitHub client ID.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Github Oauth URL.
*/
fun getGithubOauthUrl(clientId: String, state: String): String =
"https://github.com/login/oauth/authorize?scope=user:email&client_id=$clientId&state=$state"
/**
* Returns the Google Oauth URL.
*
* @param clientId The Google client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Google Oauth URL.
*/
fun getGoogleOauthUrl(clientId: String, serverUrl: String, state: String) =
"https://accounts.google.com/o/oauth2/v2/auth?client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/google?close&response_type=code&state=$state&scope=email%20profile"
/**
* Returns the Gitlab Oauth URL.
*
* @param clientId The Gitlab client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Gitlab Oauth URL.
*/
fun getGitlabOauthUrl(clientId: String, serverUrl: String, state: String): String =
"https://gitlab.com/oauth/authorize?client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/gitlab?close&response_type=code&state=$state&scope=read_user"
/** /**
* Returns the server's Terms of Service URL. * Returns the server's Terms of Service URL.
* *
......
...@@ -3,12 +3,15 @@ package chat.rocket.android.util.extensions ...@@ -3,12 +3,15 @@ package chat.rocket.android.util.extensions
import android.text.Spannable import android.text.Spannable
import android.text.Spanned import android.text.Spanned
import android.text.TextUtils import android.text.TextUtils
import android.util.Base64
import android.util.Patterns import android.util.Patterns
import android.widget.EditText import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.widget.emoji.EmojiParser import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.android.widget.emoji.EmojiTypefaceSpan import chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import org.json.JSONObject
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import java.net.URLDecoder
import java.security.SecureRandom import java.security.SecureRandom
fun String.ifEmpty(value: String): String { fun String.ifEmpty(value: String): String {
...@@ -33,7 +36,23 @@ fun EditText.erase() { ...@@ -33,7 +36,23 @@ fun EditText.erase() {
} }
} }
fun String.isEmailValid(): Boolean = Patterns.EMAIL_ADDRESS.matcher(this).matches() fun String.isEmail(): Boolean = Patterns.EMAIL_ADDRESS.matcher(this).matches()
fun String.encodeToBase64(): String {
return Base64.encodeToString(this.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
}
fun String.decodeFromBase64(): String {
return Base64.decode(this, Base64.DEFAULT).toString(charset("UTF-8"))
}
fun String.decodeUrl(): String {
return URLDecoder.decode(this, "UTF-8")
}
fun String.toJsonObject(): JSONObject {
return JSONObject(this)
}
fun generateRandomString(stringLength: Int): String { fun generateRandomString(stringLength: Int): String {
val base = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" val base = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
......
...@@ -13,7 +13,7 @@ import chat.rocket.android.R ...@@ -13,7 +13,7 @@ import chat.rocket.android.R
import kotlinx.android.synthetic.main.activity_web_view.* import kotlinx.android.synthetic.main.activity_web_view.*
import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.app_bar.*
fun Context.webViewIntent(webPageUrl: String, casToken: String): Intent { fun Context.casWebViewIntent(webPageUrl: String, casToken: String): Intent {
return Intent(this, CasWebViewActivity::class.java).apply { return Intent(this, CasWebViewActivity::class.java).apply {
putExtra(INTENT_WEB_PAGE_URL, webPageUrl) putExtra(INTENT_WEB_PAGE_URL, webPageUrl)
putExtra(INTENT_CAS_TOKEN, casToken) putExtra(INTENT_CAS_TOKEN, casToken)
...@@ -21,7 +21,7 @@ fun Context.webViewIntent(webPageUrl: String, casToken: String): Intent { ...@@ -21,7 +21,7 @@ fun Context.webViewIntent(webPageUrl: String, casToken: String): Intent {
} }
private const val INTENT_WEB_PAGE_URL = "web_page_url" private const val INTENT_WEB_PAGE_URL = "web_page_url"
private const val INTENT_CAS_TOKEN = "cas_token" const val INTENT_CAS_TOKEN = "cas_token"
class CasWebViewActivity : AppCompatActivity() { class CasWebViewActivity : AppCompatActivity() {
private lateinit var webPageUrl: String private lateinit var webPageUrl: String
...@@ -49,14 +49,14 @@ class CasWebViewActivity : AppCompatActivity() { ...@@ -49,14 +49,14 @@ class CasWebViewActivity : AppCompatActivity() {
if (web_view.canGoBack()) { if (web_view.canGoBack()) {
web_view.goBack() web_view.goBack()
} else { } else {
finishActivity(false) closeView()
} }
} }
private fun setupToolbar() { private fun setupToolbar() {
toolbar.title = getString(R.string.title_authentication) toolbar.title = getString(R.string.title_authentication)
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp) toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp)
toolbar.setNavigationOnClickListener { finishActivity(false) } toolbar.setNavigationOnClickListener { closeView() }
} }
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
...@@ -64,16 +64,16 @@ class CasWebViewActivity : AppCompatActivity() { ...@@ -64,16 +64,16 @@ class CasWebViewActivity : AppCompatActivity() {
web_view.settings.javaScriptEnabled = true web_view.settings.javaScriptEnabled = true
web_view.webViewClient = object : WebViewClient() { web_view.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
// The user can be already logged in the CAS, so check if the URL contains the "ticket" word // The user may have already been logged in the CAS, so check if the URL contains the "ticket" word
// (that means he/she is successful authenticated and we don't need to wait until the page is finished. // (that means the user is successful authenticated and we don't need to wait until the page is fully loaded).
if (url.contains("ticket")) { if (url.contains("ticket")) {
finishActivity(true) closeView(Activity.RESULT_OK)
} }
} }
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
if (url.contains("ticket")) { if (url.contains("ticket")) {
finishActivity(true) closeView(Activity.RESULT_OK)
} else { } else {
view_loading.hide() view_loading.hide()
} }
...@@ -82,13 +82,9 @@ class CasWebViewActivity : AppCompatActivity() { ...@@ -82,13 +82,9 @@ class CasWebViewActivity : AppCompatActivity() {
web_view.loadUrl(webPageUrl) web_view.loadUrl(webPageUrl)
} }
private fun finishActivity(setResultOk: Boolean) { private fun closeView(activityResult: Int = Activity.RESULT_CANCELED) {
if (setResultOk) { setResult(activityResult, Intent().putExtra(INTENT_CAS_TOKEN, casToken))
setResult(Activity.RESULT_OK, Intent().putExtra(INTENT_CAS_TOKEN, casToken)) finish()
finish()
} else {
super.onBackPressed()
}
overridePendingTransition(R.anim.hold, R.anim.slide_down) overridePendingTransition(R.anim.hold, R.anim.slide_down)
} }
} }
\ No newline at end of file
package chat.rocket.android.webview.oauth.ui
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.net.toUri
import chat.rocket.android.R
import chat.rocket.android.util.extensions.decodeUrl
import chat.rocket.android.util.extensions.toJsonObject
import kotlinx.android.synthetic.main.activity_web_view.*
import kotlinx.android.synthetic.main.app_bar.*
import org.json.JSONObject
fun Context.oauthWebViewIntent(webPageUrl: String, state: String): Intent {
return Intent(this, OauthWebViewActivity::class.java).apply {
putExtra(INTENT_WEB_PAGE_URL, webPageUrl)
putExtra(INTENT_STATE, state)
}
}
private const val INTENT_WEB_PAGE_URL = "web_page_url"
private const val INTENT_STATE = "state"
private const val JSON_CREDENTIAL_TOKEN = "credentialToken"
private const val JSON_CREDENTIAL_SECRET = "credentialSecret"
const val INTENT_OAUTH_CREDENTIAL_TOKEN = "credential_token"
const val INTENT_OAUTH_CREDENTIAL_SECRET = "credential_secret"
// Shows a WebView to the user authenticate with your Gitlab credentials.
class OauthWebViewActivity : AppCompatActivity() {
private lateinit var webPageUrl: String
private lateinit var state: String
private var isWebViewSetUp: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
webPageUrl = intent.getStringExtra(INTENT_WEB_PAGE_URL)
requireNotNull(webPageUrl) { "no web_page_url provided in Intent extras" }
state = intent.getStringExtra(INTENT_STATE)
requireNotNull(state) { "no state provided in Intent extras" }
setupToolbar()
}
override fun onResume() {
super.onResume()
if (!isWebViewSetUp) {
setupWebView()
isWebViewSetUp = true
}
}
override fun onBackPressed() {
if (web_view.canGoBack()) {
web_view.goBack()
} else {
closeView()
}
}
private fun setupToolbar() {
with(toolbar) {
title = getString(R.string.title_authentication)
setNavigationIcon(R.drawable.ic_close_white_24dp)
setNavigationOnClickListener { closeView() }
}
}
@SuppressLint("SetJavaScriptEnabled")
private fun setupWebView() {
with(web_view.settings) {
javaScriptEnabled = true
// TODO This is required to make Google OAuth work, but we shoud use Custom Tabs instead. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968
userAgentString = "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19"
}
web_view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
if (url.contains(JSON_CREDENTIAL_TOKEN) && url.contains(JSON_CREDENTIAL_SECRET)) {
if (isStateValid(url)) {
val jsonResult = url.decodeUrl()
.substringAfter("#")
.toJsonObject()
val credentialToken = getCredentialToken(jsonResult)
val credentialSecret = getCredentialSecret(jsonResult)
if (credentialToken.isNotEmpty() && credentialSecret.isNotEmpty()) {
closeView(Activity.RESULT_OK, credentialToken, credentialSecret)
}
}
}
view_loading.hide()
}
}
web_view.loadUrl(webPageUrl)
}
// If the states matches, then try to get the code, otherwise the request was created by a third party and the process should be aborted.
private fun isStateValid(url: String): Boolean =
url.substringBefore("#").toUri().getQueryParameter(INTENT_STATE) == state
private fun getCredentialToken(json: JSONObject): String =
json.optString(JSON_CREDENTIAL_TOKEN)
private fun getCredentialSecret(json: JSONObject): String =
json.optString(JSON_CREDENTIAL_SECRET)
private fun closeView(activityResult: Int = Activity.RESULT_CANCELED, credentialToken: String? = null, credentialSecret: String? = null) {
setResult(activityResult, Intent().putExtra(INTENT_OAUTH_CREDENTIAL_TOKEN, credentialToken).putExtra(INTENT_OAUTH_CREDENTIAL_SECRET, credentialSecret))
finish()
overridePendingTransition(R.anim.hold, R.anim.slide_down)
}
}
\ No newline at end of file
...@@ -113,6 +113,7 @@ ...@@ -113,6 +113,7 @@
android:layout_width="290dp" android:layout_width="290dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_facebook" android:contentDescription="@string/msg_content_description_log_in_using_facebook"
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_facebook" android:src="@drawable/ic_facebook"
...@@ -124,6 +125,7 @@ ...@@ -124,6 +125,7 @@
android:layout_width="290dp" android:layout_width="290dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_github" android:contentDescription="@string/msg_content_description_log_in_using_github"
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_github" android:src="@drawable/ic_github"
...@@ -135,6 +137,7 @@ ...@@ -135,6 +137,7 @@
android:layout_width="290dp" android:layout_width="290dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_google" android:contentDescription="@string/msg_content_description_log_in_using_google"
android:foreground="?android:attr/selectableItemBackground" android:foreground="?android:attr/selectableItemBackground"
android:src="@drawable/ic_google" android:src="@drawable/ic_google"
...@@ -146,6 +149,7 @@ ...@@ -146,6 +149,7 @@
android:layout_width="290dp" android:layout_width="290dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_linkedin" android:contentDescription="@string/msg_content_description_log_in_using_linkedin"
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_linkedin" android:src="@drawable/ic_linkedin"
...@@ -157,6 +161,7 @@ ...@@ -157,6 +161,7 @@
android:layout_width="290dp" android:layout_width="290dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_meteor" android:contentDescription="@string/msg_content_description_log_in_using_meteor"
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_meteor" android:src="@drawable/ic_meteor"
...@@ -168,6 +173,7 @@ ...@@ -168,6 +173,7 @@
android:layout_width="290dp" android:layout_width="290dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_twitter" android:contentDescription="@string/msg_content_description_log_in_using_twitter"
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_twitter" android:src="@drawable/ic_twitter"
...@@ -179,6 +185,7 @@ ...@@ -179,6 +185,7 @@
android:layout_width="290dp" android:layout_width="290dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_gitlab" android:contentDescription="@string/msg_content_description_log_in_using_gitlab"
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_gitlab" android:src="@drawable/ic_gitlab"
......
...@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME ...@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
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