Skip to content

Commit

Permalink
Merge pull request #3466 from mortbauer/feature/oidc-with-state-param…
Browse files Browse the repository at this point in the history
…eter

Add the state parameter to the oidc authentication request
  • Loading branch information
abelgardep authored Dec 17, 2021
2 parents b9d2ee2 + b826444 commit 4c040ca
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,22 @@ import com.owncloud.android.data.authentication.QUERY_PARAMETER_CODE_CHALLENGE_M
import com.owncloud.android.data.authentication.QUERY_PARAMETER_REDIRECT_URI
import com.owncloud.android.data.authentication.QUERY_PARAMETER_RESPONSE_TYPE
import com.owncloud.android.data.authentication.QUERY_PARAMETER_SCOPE
import com.owncloud.android.data.authentication.QUERY_PARAMETER_STATE
import com.owncloud.android.domain.authentication.oauth.model.ClientRegistrationRequest
import java.net.URLEncoder
import java.security.MessageDigest
import java.security.SecureRandom

class OAuthUtils {

fun generateRandomState(): String {
val secureRandom = SecureRandom()
val randomBytes = ByteArray(DEFAULT_STATE_ENTROPY)
secureRandom.nextBytes(randomBytes)
val encoding = Base64.NO_WRAP or Base64.NO_PADDING or Base64.URL_SAFE
return Base64.encodeToString(randomBytes, encoding)
}

fun generateRandomCodeVerifier(): String {
val secureRandom = SecureRandom()
val randomBytes = ByteArray(DEFAULT_CODE_VERIFIER_ENTROPY)
Expand All @@ -58,6 +67,7 @@ class OAuthUtils {
private const val ALGORITHM_SHA_256 = "SHA-256"
private const val CODE_CHALLENGE_METHOD = "S256"
private const val DEFAULT_CODE_VERIFIER_ENTROPY = 64
private const val DEFAULT_STATE_ENTROPY = 15

fun buildClientRegistrationRequest(
registrationEndpoint: String,
Expand Down Expand Up @@ -88,6 +98,7 @@ class OAuthUtils {
responseType: String,
scope: String,
codeChallenge: String,
state: String,
): Uri =
authorizationEndpoint.buildUpon()
.appendQueryParameter(QUERY_PARAMETER_REDIRECT_URI, redirectUri)
Expand All @@ -96,6 +107,7 @@ class OAuthUtils {
.appendQueryParameter(QUERY_PARAMETER_SCOPE, scope)
.appendQueryParameter(QUERY_PARAMETER_CODE_CHALLENGE, codeChallenge)
.appendQueryParameter(QUERY_PARAMETER_CODE_CHALLENGE_METHOD, CODE_CHALLENGE_METHOD)
.appendQueryParameter(QUERY_PARAMETER_STATE, state)
.build()

fun buildRedirectUri(context: Context): Uri =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import com.owncloud.android.domain.authentication.oauth.model.TokenRequest
import com.owncloud.android.domain.exceptions.NoNetworkConnectionException
import com.owncloud.android.domain.exceptions.OwncloudVersionNotSupportedException
import com.owncloud.android.domain.exceptions.ServerNotReachableException
import com.owncloud.android.domain.exceptions.StateMismatchException
import com.owncloud.android.domain.exceptions.UnauthorizedException
import com.owncloud.android.domain.server.model.AuthenticationMethod
import com.owncloud.android.domain.server.model.ServerInfo
Expand Down Expand Up @@ -451,7 +452,8 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
clientId = clientId,
responseType = ResponseType.CODE.string,
scope = if (oidcSupported) OAUTH2_OIDC_SCOPE else "",
codeChallenge = oauthViewModel.codeChallenge
codeChallenge = oauthViewModel.codeChallenge,
state = oauthViewModel.oidcState
)

customTabsIntent.launchUrl(
Expand All @@ -469,18 +471,24 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted

private fun handleGetAuthorizationCodeResponse(intent: Intent) {
val authorizationCode = intent.data?.getQueryParameter("code")
val state = intent.data?.getQueryParameter("state")

if (authorizationCode != null) {
Timber.d("Authorization code received [$authorizationCode]. Let's exchange it for access token")
exchangeAuthorizationCodeForTokens(authorizationCode)
if (state != oauthViewModel.oidcState){
Timber.e("OAuth request to get authorization code failed. State mismatching, maybe somebody is trying a CSRF attack.")
updateOAuthStatusIconAndText(StateMismatchException())
} else {
val authorizationError = intent.data?.getQueryParameter("error")
val authorizationErrorDescription = intent.data?.getQueryParameter("error_description")
if (authorizationCode != null) {
Timber.d("Authorization code received [$authorizationCode]. Let's exchange it for access token")
exchangeAuthorizationCodeForTokens(authorizationCode)
} else {
val authorizationError = intent.data?.getQueryParameter("error")
val authorizationErrorDescription = intent.data?.getQueryParameter("error_description")

Timber.e("OAuth request to get authorization code failed. Error: [$authorizationError]. Error description: [$authorizationErrorDescription]")
val authorizationException =
if (authorizationError == "access_denied") UnauthorizedException() else Throwable()
updateOAuthStatusIconAndText(authorizationException)
Timber.e("OAuth request to get authorization code failed. Error: [$authorizationError]. Error description: [$authorizationErrorDescription]")
val authorizationException =
if (authorizationError == "access_denied") UnauthorizedException() else Throwable()
updateOAuthStatusIconAndText(authorizationException)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class OAuthViewModel(

val codeVerifier: String = OAuthUtils().generateRandomCodeVerifier()
val codeChallenge: String = OAuthUtils().generateCodeChallenge(codeVerifier)
val oidcState: String = OAuthUtils().generateRandomState()

private val _oidcDiscovery = MediatorLiveData<Event<UIResult<OIDCServerConfiguration>>>()
val oidcDiscovery: LiveData<Event<UIResult<OIDCServerConfiguration>>> = _oidcDiscovery
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ const val QUERY_PARAMETER_RESPONSE_TYPE = "response_type"
const val QUERY_PARAMETER_SCOPE = "scope"
const val QUERY_PARAMETER_CODE_CHALLENGE = "code_challenge"
const val QUERY_PARAMETER_CODE_CHALLENGE_METHOD = "code_challenge_method"
const val QUERY_PARAMETER_STATE = "state"
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* ownCloud Android client application
*
* @author David González Verdugo
* Copyright (C) 2020 ownCloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.owncloud.android.domain.exceptions

import java.lang.Exception

class StateMismatchException : Exception()

0 comments on commit 4c040ca

Please sign in to comment.