@@ -16,11 +16,18 @@ import com.google.api.client.util.store.MemoryDataStoreFactory
16
16
import io.ktor.client.HttpClientConfig
17
17
import io.ktor.client.plugins.auth.Auth
18
18
import io.ktor.client.plugins.auth.providers.BearerTokens
19
+ import io.ktor.client.plugins.auth.providers.RefreshTokensParams
19
20
import io.ktor.client.plugins.auth.providers.bearer
21
+ import io.ktor.client.statement.request
20
22
import io.ktor.http.HttpHeaders
21
23
import io.ktor.http.HttpMessage
24
+ import io.ktor.http.URLProtocol
25
+ import io.ktor.http.Url
22
26
import io.ktor.http.auth.HttpAuthHeader
23
27
import io.ktor.http.auth.parseAuthorizationHeader
28
+ import io.ktor.http.buildUrl
29
+ import io.ktor.http.isSecure
30
+ import io.ktor.http.takeFrom
24
31
import kotlinx.coroutines.Dispatchers
25
32
import kotlinx.coroutines.withContext
26
33
@@ -45,35 +52,18 @@ actual object ModelixAuthClient {
45
52
return this
46
53
}
47
54
48
- suspend fun authorize (modelixServerUrl : String ): Credential {
49
- val oidcUrl = modelixServerUrl.trimEnd(' /' ) + " /realms/modelix/protocol/openid-connect"
50
- return authorize(
51
- clientId = " external-mps" ,
52
- scopes = listOf (" email" ),
53
- authUrl = " $oidcUrl /auth" ,
54
- tokenUrl = " $oidcUrl /token" ,
55
- authRequestBrowser = null ,
56
- )
57
- }
58
-
59
- suspend fun authorize (
60
- clientId : String ,
61
- scopes : List <String >,
62
- authUrl : String ,
63
- tokenUrl : String ,
64
- authRequestBrowser : ((url: String ) -> Unit )? ,
65
- ): Credential {
55
+ suspend fun authorize (config : OAuthConfig ): Credential {
66
56
return withContext(Dispatchers .IO ) {
67
57
val flow = AuthorizationCodeFlow .Builder (
68
58
BearerToken .authorizationHeaderAccessMethod(),
69
59
HTTP_TRANSPORT ,
70
60
JSON_FACTORY ,
71
- GenericUrl (tokenUrl),
72
- ClientParametersAuthentication (clientId, null ),
73
- clientId,
74
- authUrl ,
61
+ GenericUrl (config. tokenUrl),
62
+ ClientParametersAuthentication (config. clientId, config.clientSecret ),
63
+ config. clientId,
64
+ config.authorizationUrl ,
75
65
)
76
- .setScopes(scopes)
66
+ .setScopes(config. scopes)
77
67
.enablePKCE()
78
68
.setDataStoreFactory(DATA_STORE_FACTORY )
79
69
.build()
@@ -82,10 +72,10 @@ actual object ModelixAuthClient {
82
72
if (existingTokens?.isExpired() == false ) return @withContext existingTokens
83
73
84
74
val receiver: LocalServerReceiver = LocalServerReceiver .Builder ().setHost(" 127.0.0.1" ).build()
85
- val browser = authRequestBrowser ?.let {
75
+ val browser = config.authRequestHandler ?.let {
86
76
object : AuthorizationCodeInstalledApp .Browser {
87
77
override fun browse (url : String ) {
88
- it(url)
78
+ it.browse (url)
89
79
}
90
80
}
91
81
} ? : AuthorizationCodeInstalledApp .DefaultBrowser ()
@@ -100,21 +90,17 @@ actual object ModelixAuthClient {
100
90
@Suppress(" UndocumentedPublicFunction" ) // already documented in the expected declaration
101
91
actual fun installAuth (
102
92
config : HttpClientConfig <* >,
103
- baseUrl : String ,
104
- authTokenProvider : (suspend () -> String? )? ,
105
- authRequestBrowser : ((url: String ) -> Unit )? ,
93
+ authConfig : IAuthConfig ,
106
94
) {
107
- if (authTokenProvider != null ) {
108
- installAuthWithAuthTokenProvider(config, authTokenProvider)
109
- } else {
110
- installAuthWithPKCEFlow(config, baseUrl, authRequestBrowser)
95
+ when (authConfig) {
96
+ is TokenProviderAuthConfig -> installAuthWithAuthTokenProvider(config, authConfig.provider)
97
+ is OAuthConfig -> installAuthWithPKCEFlow(config, authConfig)
111
98
}
112
99
}
113
100
114
101
private fun installAuthWithPKCEFlow (
115
102
config : HttpClientConfig <* >,
116
- baseUrl : String ,
117
- authRequestBrowser : ((url: String ) -> Unit )? ,
103
+ authConfig : OAuthConfig ,
118
104
) {
119
105
config.apply {
120
106
install(Auth ) {
@@ -127,32 +113,16 @@ actual object ModelixAuthClient {
127
113
// The model server tells the client where to get a token
128
114
129
115
if (wwwAuthenticate.parameter(" error" ) != " invalid_token" ) return @let null
130
- val authUrl = wwwAuthenticate.parameter(" authorization_uri" ) ? : return @let null
131
- val tokenUrl = wwwAuthenticate.parameter(" token_uri" ) ? : return @let null
116
+ val updatedConfig = authConfig.copy(
117
+ authorizationUrl = authConfig.authorizationUrl ? : useSameProtocol(wwwAuthenticate.parameter(" authorization_uri" ) ? : return @let null ),
118
+ tokenUrl = authConfig.tokenUrl ? : useSameProtocol(wwwAuthenticate.parameter(" token_uri" ) ? : return @let null ),
119
+ )
132
120
val realm = wwwAuthenticate.parameter(" realm" )
133
121
val description = wwwAuthenticate.parameter(" error_description" )
134
- authorize(
135
- clientId = " modelix-sync-plugin" ,
136
- scopes = listOf (" sync" ),
137
- authUrl = authUrl,
138
- tokenUrl = tokenUrl,
139
- authRequestBrowser = authRequestBrowser,
140
- )
141
- } ? : let {
142
- // legacy keycloak specific URLs
122
+ authorize(updatedConfig)
123
+ } ? : authorize(authConfig)
143
124
144
- var url = baseUrl
145
- if (! url.endsWith(" /" )) url + = " /"
146
- // XXX Detecting and removing "/model/" is workaround for when the model server
147
- // is used in Modelix workspaces and reachable behind the sub path /model/".
148
- // When the model server is reachable at https://example.org/model/,
149
- // Keycloak is expected to be reachable under https://example.org/realms/
150
- // See https://github.com/modelix/modelix.kubernetes/blob/60f7db6533c3fb82209b1a6abb6836923f585672/proxy/nginx.conf#L14
151
- // and https://github.com/modelix/modelix.kubernetes/blob/60f7db6533c3fb82209b1a6abb6836923f585672/proxy/nginx.conf#L41
152
- // TODO MODELIX-975 remove this check and replace with configuration.
153
- if (url.endsWith(" /model/" )) url = url.substringBeforeLast(" /model/" )
154
- authorize(url)
155
- }
125
+ println (" Access Token: " + tokens.accessToken)
156
126
157
127
BearerTokens (tokens.accessToken, tokens.refreshToken)
158
128
}
@@ -165,4 +135,21 @@ actual object ModelixAuthClient {
165
135
return headers[HttpHeaders .WWWAuthenticate ]
166
136
?.let { parseAuthorizationHeader(it) as ? HttpAuthHeader .Parameterized }
167
137
}
138
+
139
+ /* *
140
+ * In test environments https often doesn't have a valid certificate.
141
+ * Use http for authorization, if the original request itself uses http.
142
+ */
143
+ private fun RefreshTokensParams.useSameProtocol (url : String ): String {
144
+ val needSecureProtocol = response.request.url.protocol.isSecure()
145
+ if (Url (url).protocol.isSecure() == needSecureProtocol) return url
146
+ return buildUrl {
147
+ takeFrom(url)
148
+ protocol = when (protocol) {
149
+ URLProtocol .HTTPS , URLProtocol .HTTP -> if (needSecureProtocol) URLProtocol .HTTPS else URLProtocol .HTTP
150
+ URLProtocol .WSS , URLProtocol .WS -> if (needSecureProtocol) URLProtocol .WSS else URLProtocol .WS
151
+ else -> protocol
152
+ }
153
+ }.toString()
154
+ }
168
155
}
0 commit comments