diff --git a/NEWS.md b/NEWS.md index 4e47f2f2..adf496d5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ ## Added * internal functions to serialize and load the session information like connection, process collection, etc. +* Support for the OpenID Connect Client Credentials flow ## Changed * argument bounding box tries to extract the EPSG code it EPSGCode was provided as WKT2 diff --git a/R/authentication.R b/R/authentication.R index 66c0ed8f..9f7d8785 100644 --- a/R/authentication.R +++ b/R/authentication.R @@ -168,6 +168,7 @@ BasicAuth <- R6Class( #' \itemize{ #' \item{authorization_code} #' \item{authorization_code+pkce} +#' \item{urn:ietf:params:oauth:grant-type:device_code} #' \item{urn:ietf:params:oauth:grant-type:device_code+pkce} #' } #' @@ -616,6 +617,45 @@ OIDCAuthCodeFlow <- R6Class( ) ) +# [OIDCClientCredentialsFlow] ---- +OIDCClientCredentialsFlow <- R6Class( + "OIDCClientCredentialsFlow", + inherit = AbstractOIDCAuthentication, + # public ==== + public = list( + # functions #### + login = function() { + + client <- oauth_client( + id = private$client_id, + secret = private$secret, + token_url = private$endpoints$token_endpoint, + name = "openeo-r-oidc-auth" + ) + + private$auth = oauth_flow_client_credentials( + client = client, + scope = paste0(private$scopes, collapse = " ") + ) + + invisible(self) + } + ), + # private ==== + private = list( + # attributes #### + grant_type = "client_credentials", # not used internally by httr2, but maybe useful in openeo + + # functions #### + isGrantTypeSupported = function(grant_types) { + if (!"client_credentials" %in% grant_types) { + stop("Client Credentials flow is not supported by the authentication provider") + } + invisible(TRUE) + } + ) +) + # utility functions ---- .get_oidc_provider = function(provider, oidc_providers=NULL) { if (is.null(oidc_providers)) { diff --git a/R/client.R b/R/client.R index c416f4dc..2df0d61e 100644 --- a/R/client.R +++ b/R/client.R @@ -375,9 +375,11 @@ OpenEOClient <- R6Class( # probably fetch resolve the potential string into a provider here provider = .get_oidc_provider(provider) + auth_code = "authorization_code" auth_pkce = "authorization_code+pkce" device_pkce = "urn:ietf:params:oauth:grant-type:device_code+pkce" device_code = "urn:ietf:params:oauth:grant-type:device_code" + client_credentials = "client_credentials" has_default_clients = "default_clients" %in% names(provider) && length(provider[["default_clients"]]) > 0 client_id_given = "client_id" %in% names(config) @@ -388,14 +390,18 @@ OpenEOClient <- R6Class( } full_credentials = all(c("client_id","secret") %in% names(config)) - is_auth_code = length(config$grant_type) > 0 && config$grant_type == 'authorization_code' + is_auth_code = length(config$grant_type) > 0 && config$grant_type == auth_code + is_client_credentials = length(config$grant_type) > 0 && config$grant_type == client_credentials - # either credentials are set and / or authorization_code as grant_type - if (full_credentials && (is_auth_code || is.null(config$grant_type))) { + # either credentials are set and / or authorization_code or client_credentials as grant_type + if (full_credentials && is_client_credentials) { + private$auth_client = OIDCClientCredentialsFlow$new(provider = provider, config = config, force=TRUE) + } + else if (full_credentials && (is_auth_code || is.null(config$grant_type))) { private$auth_client = OIDCAuthCodeFlow$new(provider = provider, config = config, force=TRUE) } - else if (is_auth_code) { - stop("For grant type 'authorization_code' a client_id and secret must be provided") + else if (is_auth_code || is_client_credentials) { + stop("For grant type 'authorization_code' and 'client_credentials' a client_id and secret must be provided") } else if (client_id_given && has_default_clients) { default_clients = provider[["default_clients"]] @@ -439,12 +445,15 @@ OpenEOClient <- R6Class( stop("Please provide a client id or a valid combination of client_id and grant_type.") } } - - if (device_pkce == config$grant_type) { + + has_grant = "grant_type" %in% names(config) + if (has_grant && device_pkce == config$grant_type) { private$auth_client = OIDCDeviceCodeFlowPkce$new(provider=provider, config = config) - } else if (device_code == config$grant_type) { + } else if (has_grant && device_code == config$grant_type) { private$auth_client = OIDCDeviceCodeFlow$new(provider=provider, config = config) - } else if (is.null(config$grant_type) || auth_pkce == config$grant_type) { + } else if (has_grant && client_credentials == config$grant_type) { + private$auth_client = OIDCClientCredentialsFlow$new(provider=provider, config = config) + } else if (is.null(config$grant_type) || (has_grant && auth_pkce == config$grant_type)) { private$auth_client = OIDCAuthCodeFlowPKCE$new(provider=provider, config = config) } diff --git a/man/OIDCAuth.Rd b/man/OIDCAuth.Rd index 0d36d4fb..d089ed7a 100644 --- a/man/OIDCAuth.Rd +++ b/man/OIDCAuth.Rd @@ -20,6 +20,7 @@ This client supports the following interaction mechanisms (grant types): \itemize{ \item{authorization_code} \item{authorization_code+pkce} +\item{urn:ietf:params:oauth:grant-type:device_code} \item{urn:ietf:params:oauth:grant-type:device_code+pkce} } @@ -47,6 +48,11 @@ the console or if R runs in the interactive mode the internet browser will be op This mechanism uses a designated device code for human confirmation. It is closely related to the device_code+pkce code flow, but without the additional PKCE negotiation. } + +\subsection{client_credentials}{ +This mechanism is used to verify a certain client, not a specific user. It requires a client id and secret and is meant for machine-to-machine workflows. +} + } \section{Fields}{ diff --git a/man/login.Rd b/man/login.Rd index 698682cc..cdf77edb 100644 --- a/man/login.Rd +++ b/man/login.Rd @@ -52,6 +52,7 @@ list. You can then use the following values: \item authorization_code+pkce \item urn:ietf:params:oauth:grant-type:device_code \item urn:ietf:params:oauth:grant-type:device_code+pkce +\item client_credentials } } \section{Configuration options}{ diff --git a/vignettes/openeo-03-package-software-architecture.Rmd b/vignettes/openeo-03-package-software-architecture.Rmd index 136a72db..c960ec09 100644 --- a/vignettes/openeo-03-package-software-architecture.Rmd +++ b/vignettes/openeo-03-package-software-architecture.Rmd @@ -58,7 +58,7 @@ R6 is an object oriented programming style like S3 or S4 in R that is based on [ # Authentication -To get access to the computation capabilities and user stored data openEO you need to be a registered user at the openEO back-end, where you want to carry out your analysis. In order to proof that to the system you need to be authenticated. The openEO API offers Open ID Connect (OIDC) and Basic Authentication as authentication methods. In this package the we use primarily OIDC, but also offer Basic Authentication for legacy support. As OIDC is based on OAuth2 there are several different sub mechanisms, e.g. Authcode Flow or Device Code Flow with or without PKCE. The mechanisms are covered by the httr2 package `httr2::oauth_flow_device()`, which is used to negotiate the authentication and to obtain the required access token. In the code the different authentication methods are realized by the different R6 classes that share a common interface `IAuth`. Inheriting classes implement and overload those functions so that by using the function `..$login()` or the active field access_token all objects behave in the same manor and ultimately provide the access token. +To get access to the computation capabilities and user stored data openEO you need to be a registered user at the openEO back-end, where you want to carry out your analysis. In order to proof that to the system you need to be authenticated. The openEO API offers Open ID Connect (OIDC) and Basic Authentication as authentication methods. In this package the we use primarily OIDC, but also offer Basic Authentication for legacy support. As OIDC is based on OAuth2 there are several different sub mechanisms, e.g. Auth Code Flow or Device Code Flow with or without PKCE. The mechanisms are covered by the httr2 package `httr2::oauth_flow_device()`, which is used to negotiate the authentication and to obtain the required access token. In the code the different authentication methods are realized by the different R6 classes that share a common interface `IAuth`. Inheriting classes implement and overload those functions so that by using the function `..$login()` or the active field access_token all objects behave in the same manor and ultimately provide the access token. # Visual Components