Skip to content
This repository has been archived by the owner on Jun 7, 2020. It is now read-only.

[NEW] SAML support #1326

Merged
merged 3 commits into from
Jun 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,22 @@
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">

<activity
android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation"
android:screenOrientation="portrait"
android:theme="@style/AuthenticationTheme"
android:windowSoftInputMode="adjustResize">

<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />

Expand All @@ -50,25 +51,31 @@
android:scheme="https" />
</intent-filter>
</activity>

<activity
android:name=".server.ui.ChangeServerActivity"
android:theme="@style/AuthenticationTheme" />

<activity
android:name=".main.ui.MainActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />

<activity
android:name=".webview.ui.WebViewActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />

<activity
android:name=".webview.cas.ui.CasWebViewActivity"
android:name=".webview.sso.ui.SsoWebViewActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />

<activity
android:name=".webview.oauth.ui.OauthWebViewActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />

<activity
android:name=".chatroom.ui.ChatRoomActivity"
android:theme="@style/AppTheme"
Expand All @@ -95,6 +102,7 @@
<category android:name="${applicationId}" />
</intent-filter>
</receiver>

<receiver
android:name=".push.DirectReplyReceiver"
android:enabled="true"
Expand All @@ -111,13 +119,15 @@
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>

<service
android:name=".push.GcmListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>

<service
android:name=".chatroom.service.MessageService"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ 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 TYPE_LOGIN_DEEP_LINK = 3
private const val TYPE_LOGIN_SAML = 2
private const val TYPE_LOGIN_OAUTH = 3
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"
Expand Down Expand Up @@ -82,14 +83,19 @@ class LoginPresenter @Inject constructor(
}
}

fun authenticateWithCas(token: String) {
credentialToken = token
fun authenticateWithCas(casToken: String) {
credentialToken = casToken
doAuthentication(TYPE_LOGIN_CAS)
}

fun authenticateWithOauth(token: String, secret: String) {
credentialToken = token
credentialSecret = secret
fun authenticateWithSaml(samlToken: String) {
credentialToken = samlToken
doAuthentication(TYPE_LOGIN_SAML)
}

fun authenticateWithOauth(oauthToken: String, oauthSecret: String) {
credentialToken = oauthToken
credentialSecret = oauthSecret
doAuthentication(TYPE_LOGIN_OAUTH)
}

Expand All @@ -99,7 +105,6 @@ class LoginPresenter @Inject constructor(
deepLinkUserId = deepLinkInfo.userId
deepLinkToken = deepLinkInfo.token
tokenRepository.save(serverUrl, Token(deepLinkUserId, deepLinkToken))

doAuthentication(TYPE_LOGIN_DEEP_LINK)
}

Expand All @@ -124,8 +129,11 @@ class LoginPresenter @Inject constructor(

private fun setupCasView() {
if (settings.isCasAuthenticationEnabled()) {
val token = generateRandomString(17)
view.setupCasButtonListener(settings.casLoginUrl().casUrl(currentServer, token), token)
val casToken = generateRandomString(17)
view.setupCasButtonListener(
settings.casLoginUrl().casUrl(currentServer, casToken),
casToken
)
view.showCasButton()
}
}
Expand Down Expand Up @@ -216,7 +224,7 @@ class LoginPresenter @Inject constructor(
// totalSocialAccountsEnabled++
}
if (settings.isTwitterAuthenticationEnabled()) {
//TODO: Remove until we have this implemented
//TODO: Remove until Twitter provides support to OAuth2
// view.enableLoginByTwitter()
// totalSocialAccountsEnabled++
}
Expand Down Expand Up @@ -261,8 +269,23 @@ class LoginPresenter @Inject constructor(
customOauthUrl,
state,
serviceName,
getCustomOauthServiceNameColor(service),
getCustomOauthButtonColor(service)
getServiceNameColor(service),
getServiceButtonColor(service)
)
totalSocialAccountsEnabled++
}
}

getSamlServices(services).let {
val samlToken = generateRandomString(17)

for (service in it) {
view.addSamlServiceButton(
currentServer.samlUrl(getSamlProvider(service), samlToken),
samlToken,
getSamlServiceName(service),
getServiceNameColor(service),
getServiceButtonColor(service)
)
totalSocialAccountsEnabled++
}
Expand Down Expand Up @@ -307,6 +330,10 @@ class LoginPresenter @Inject constructor(
delay(3, TimeUnit.SECONDS)
client.loginWithCas(credentialToken)
}
TYPE_LOGIN_SAML -> {
delay(3, TimeUnit.SECONDS)
client.loginWithSaml(credentialToken)
}
TYPE_LOGIN_OAUTH -> {
client.loginWithOauth(credentialToken, credentialSecret)
}
Expand All @@ -319,7 +346,7 @@ class LoginPresenter @Inject constructor(
}
}
else -> {
throw IllegalStateException("Expected TYPE_LOGIN_USER_EMAIL, TYPE_LOGIN_CAS, TYPE_LOGIN_OAUTH or TYPE_LOGIN_DEEP_LINK")
throw IllegalStateException("Expected TYPE_LOGIN_USER_EMAIL, TYPE_LOGIN_CAS,TYPE_LOGIN_SAML, TYPE_LOGIN_OAUTH or TYPE_LOGIN_DEEP_LINK")
}
}
}
Expand Down Expand Up @@ -365,6 +392,18 @@ class LoginPresenter @Inject constructor(
}.toString()
}

private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> {
return listMap.filter { map -> map["service"] == "saml" }
}

private fun getSamlServiceName(service: Map<String, Any>): String {
return service["buttonLabelText"].toString()
}

private fun getSamlProvider(service: Map<String, Any>): String {
return (service["clientConfig"] as Map<*, *>)["provider"].toString()
}

private fun getCustomOauthServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> {
return listMap.filter { map -> map["custom"] == true }
}
Expand All @@ -389,11 +428,11 @@ class LoginPresenter @Inject constructor(
return service["scope"].toString()
}

private fun getCustomOauthButtonColor(service: Map<String, Any>): Int {
private fun getServiceButtonColor(service: Map<String, Any>): Int {
return service["buttonColor"].toString().parseColor()
}

private fun getCustomOauthServiceNameColor(service: Map<String, Any>): Int {
private fun getServiceNameColor(service: Map<String, Any>): Int {
return service["buttonLabelColor"].toString().parseColor()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ interface LoginView : LoadingView, MessageView {
* Enables and shows the oauth view if there is login via social accounts enabled by the server settings.
*
* REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle],
* [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter], [enableLoginByGitlab] or [addCustomOauthServiceButton]) for the oauth view.
* [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter], [enableLoginByGitlab], [addCustomOauthServiceButton] or [addSamlServiceButton]) for the oauth view.
* If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s).
*/
fun enableOauthView()
Expand Down Expand Up @@ -197,7 +197,7 @@ interface LoginView : LoadingView, MessageView {
* @state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
* @serviceName The custom OAuth service name.
* @serviceNameColor The custom OAuth service name color (just stylizing).
* @buttonColor The color of the custom OAuth button (just stylizing).
* @buttonColor The custom OAuth button color (just stylizing).
* @see [enableOauthView]
*/
fun addCustomOauthServiceButton(
Expand All @@ -208,6 +208,23 @@ interface LoginView : LoadingView, MessageView {
buttonColor: Int
)

/**
* Adds a SAML button in the oauth view.
*
* @samlUrl The SAML url to sets up the button (the listener).
* @serviceName The SAML service name.
* @serviceNameColor The SAML service name color (just stylizing).
* @buttonColor The SAML button color (just stylizing).
* @see [enableOauthView]
*/
fun addSamlServiceButton(
samlUrl: String,
samlToken: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
)

/**
* Setups the FloatingActionButton to show more social accounts views (expanding the oauth view interface to show the remaining view(s)).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.extensions.*
import chat.rocket.android.webview.cas.ui.INTENT_CAS_TOKEN
import chat.rocket.android.webview.cas.ui.casWebViewIntent
import chat.rocket.android.webview.sso.ui.INTENT_SSO_TOKEN
import chat.rocket.android.webview.sso.ui.ssoWebViewIntent
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
Expand All @@ -42,12 +42,12 @@ import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import timber.log.Timber
import javax.inject.Inject


internal const val REQUEST_CODE_FOR_CAS = 1
internal const val REQUEST_CODE_FOR_OAUTH = 2
internal const val MULTIPLE_CREDENTIALS_READ = 3
internal const val NO_CREDENTIALS_EXIST = 4
internal const val SAVE_CREDENTIALS = 5
internal const val REQUEST_CODE_FOR_SAML = 2
internal const val REQUEST_CODE_FOR_OAUTH = 3
internal const val MULTIPLE_CREDENTIALS_READ = 4
internal const val NO_CREDENTIALS_EXIST = 5
internal const val SAVE_CREDENTIALS = 6

lateinit var googleApiClient: GoogleApiClient

Expand Down Expand Up @@ -121,7 +121,10 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
if (data != null) {
when (requestCode) {
REQUEST_CODE_FOR_CAS -> data.apply {
presenter.authenticateWithCas(getStringExtra(INTENT_CAS_TOKEN))
presenter.authenticateWithCas(getStringExtra(INTENT_SSO_TOKEN))
}
REQUEST_CODE_FOR_SAML -> data.apply {
presenter.authenticateWithSaml(getStringExtra(INTENT_SSO_TOKEN))
}
REQUEST_CODE_FOR_OAUTH -> {
isOauthSuccessful = true
Expand Down Expand Up @@ -371,7 +374,7 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
ui { activity ->
button_cas.setOnClickListener {
startActivityForResult(
activity.casWebViewIntent(casUrl, casToken),
activity.ssoWebViewIntent(casUrl, casToken),
REQUEST_CODE_FOR_CAS
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
Expand Down Expand Up @@ -566,7 +569,7 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
buttonColor: Int
) {
ui { activity ->
val button = getCustomOauthButton(serviceName, serviceNameColor, buttonColor)
val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
social_accounts_container.addView(button)

button.setOnClickListener {
Expand All @@ -579,6 +582,27 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
}
}

override fun addSamlServiceButton(
samlUrl: String,
samlToken: String,
serviceName: String,
serviceNameColor: Int,
buttonColor: Int
) {
ui { activity ->
val button = getCustomServiceButton(serviceName, serviceNameColor, buttonColor)
social_accounts_container.addView(button)

button.setOnClickListener {
startActivityForResult(
activity.ssoWebViewIntent(samlUrl, samlToken),
REQUEST_CODE_FOR_SAML
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}

override fun setupFabListener() {
ui {
button_fab.isVisible = true
Expand Down Expand Up @@ -696,9 +720,9 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
}

/**
* Gets a stylized custom OAuth button.
* Gets a stylized custom service button.
*/
private fun getCustomOauthButton(
private fun getCustomServiceButton(
buttonText: String,
buttonTextColor: Int,
buttonBgColor: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ fun String.safeUrl(): String {

fun String.serverLogoUrl(favicon: String) = "${removeTrailingSlash()}/$favicon"

fun String.casUrl(serverUrl: String, token: String) =
"${removeTrailingSlash()}?service=${serverUrl.removeTrailingSlash()}/_cas/$token"
fun String.casUrl(serverUrl: String, casToken: String) =
"${removeTrailingSlash()}?service=${serverUrl.removeTrailingSlash()}/_cas/$casToken"

fun String.samlUrl(provider: String, samlToken: String) =
"${removeTrailingSlash()}/_saml/authorize/$provider/$samlToken"

fun String.termsOfServiceUrl() = "${removeTrailingSlash()}/terms-of-service"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ class OauthWebViewActivity : AppCompatActivity() {
private lateinit var state: String
private var isWebViewSetUp: Boolean = false


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
Expand Down
Loading