Add auth method to simplify (o)authorization
Change-Id: I318e27375dfece96d0d80f35cd2c2b5bb3b167df
diff --git a/DESCRIPTION b/DESCRIPTION
index cce9334..dcdb680 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -45,6 +45,7 @@
keyring,
utils,
httr,
+ httr2,
curl,
methods,
PTXQC,
diff --git a/NAMESPACE b/NAMESPACE
index e7aa324..06a501f 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -36,6 +36,7 @@
exportClasses(KorAPCorpusStats)
exportClasses(KorAPQuery)
exportMethods(apiCall)
+exportMethods(auth)
exportMethods(clearAccessToken)
exportMethods(clearCache)
exportMethods(collocationAnalysis)
@@ -100,6 +101,8 @@
importFrom(ggplot2,theme)
importFrom(httr,build_url)
importFrom(httr,parse_url)
+importFrom(httr2,oauth_client)
+importFrom(httr2,oauth_flow_auth_code)
importFrom(jsonlite,fromJSON)
importFrom(lubridate,year)
importFrom(magrittr,"%>%")
diff --git a/R/KorAPConnection.R b/R/KorAPConnection.R
index 6739aee..9a4a5cb 100644
--- a/R/KorAPConnection.R
+++ b/R/KorAPConnection.R
@@ -33,7 +33,9 @@
#' @param accessToken OAuth2 access token. For queries on corpus parts with restricted
#' access (e.g. textual queries on IPR protected data), you need to authorize
#' your application with an access token.
-#' How to obtain an access token for the DeReKo KorAP instance is explained in the
+#' You can obtain an access token using the [auth()] method.
+#'
+#' More details are explained in the
#' [authorization section](https://github.com/KorAP/RKorAPClient#authorization)
#' of the RKorAPClient Readme on GitHub.
#'
@@ -122,42 +124,114 @@
setGeneric("persistAccessToken", function(kco, ...) standardGeneric("persistAccessToken") )
+#' Persist current access token in keyring
+#'
+#' @param kco KorAPConnection object
+#' @param accessToken access token to be persisted. If not supplied, the current access token of the KorAPConnection object will be used.
+#' @return KorAPConnection object.
+#'
#' @aliases persistAccessToken
-#' @rdname KorAPConnection-class
+#'
#' @import keyring
#' @export
+#'
#' @examples
#' \dontrun{
-#'
#' kco <- new("KorAPConnection", accessToken="e739u6eOzkwADQPdVChxFg")
#' persistAccessToken(kco)
+#'
+#' kco <- new("KorAPConnection") %>% auth(app_id="<my application id>") %>% persistAccessToken()
#' }
#'
+#' @seealso [clearAccessToken()], [auth()]
+#'
setMethod("persistAccessToken", "KorAPConnection", function(kco, accessToken = kco@accessToken) {
if (is.null(accessToken))
stop("It seems that you have not supplied any access token that could be persisted.", call. = FALSE)
kco@accessToken <- accessToken
key_set_with_value(accessTokenServiceName, kco@KorAPUrl, accessToken)
+ return(kco)
})
setGeneric("clearAccessToken", function(kco) standardGeneric("clearAccessToken") )
+#' Clear access token from keyring and KorAPConnection object
+#'
#' @aliases clearAccessToken
-#' @rdname KorAPConnection-class
#' @import keyring
+#' @param kco KorAPConnection object
+#' @return KorAPConnection object with access token set to `NULL`.
#' @export
#' @examples
-#' \dontrun{
#'
+#' \dontrun{
#' kco <- new("KorAPConnection")
-#' clearAccessToken(kco)
+#' kco <- clearAccessToken(kco)
#' }
#'
+#' @seealso [persistAccessToken()]
+#'
setMethod("clearAccessToken", "KorAPConnection", function(kco) {
key_delete(accessTokenServiceName, kco@KorAPUrl)
+ kco@accessToken <- NULL
+ kco
})
+generic_kor_app_id = "99FbPHH7RrN36hbndF7b6f"
+
+
+setGeneric("auth", function(kco, app_id = generic_kor_app_id, scope = "search match_info") standardGeneric("auth") )
+
+#' Authorize RKorAPClient
+#'
+#' @aliases auth
+#'
+#' @description
+#' `r lifecycle::badge("experimental")`
+#'
+#' Authorize RKorAPClient to make KorAP queries and download results on behalf of the user.
+#'
+#' @param kco KorAPConnection object
+#' @param app_id OAuth2 application id. Defaults to the generic KorAP client application id.
+#' @param scope OAuth2 scope. Defaults to "search match_info".
+#' @return KorAPConnection object with access token set in `@accessToken`.
+#'
+#' @importFrom httr2 oauth_client oauth_flow_auth_code
+#' @examples
+#' \dontrun{
+#' kco <- new("KorAPConnection", verbose = TRUE) %>% auth()
+#' df <- collocationAnalysis(kco, "focus([marmot/p=ADJA] {Ameisenplage})", leftContextSize=1, rightContextSize=0)
+#' }
+#'
+#' @seealso [persistAccessToken()], [clearAccessToken()]
+#'
+#' @export
+setMethod("auth", "KorAPConnection", function(kco, app_id = generic_kor_app_id, scope = "search match_info") {
+ if ( kco@KorAPUrl != "https://korap.ids-mannheim.de/" & app_id == generic_kor_app_id) {
+ warning(paste("You can use the default app_id only for the IDS Mannheim KorAP main instance for querying DeReKo. Please provide your own app_id for accesing", kco@KorAPUrl))
+ return(kco)
+ }
+ if (is.null(kco@accessToken) || is.null(kco@welcome)) { # if access token is not set or invalid
+ kco@accessToken <- (
+ httr2::oauth_client(
+ id = app_id,
+ token_url = paste0(kco@apiUrl, "oauth2/token")
+ ) %>%
+ httr2::oauth_flow_auth_code(
+ scope = scope,
+ auth_url = paste0(kco@KorAPUrl, "settings/oauth/authorize"),
+ redirect_uri = "http://localhost:1410"
+ )
+ )$access_token
+ } else {
+ log_info(kco@verbose, "Client authorized. Access token already set.")
+ }
+ return(kco)
+})
+
+
+
#' @import keyring
getAccessToken <- function(KorAPUrl) {
keyList <- tryCatch(withCallingHandlers(key_list(service = accessTokenServiceName),
diff --git a/Readme.md b/Readme.md
index e540266..a7a8a71 100644
--- a/Readme.md
+++ b/Readme.md
@@ -59,12 +59,10 @@
### Identify *in … setzen* light verb constructions by using the new `collocationAnalysis` function
-[![Lifecycle:experimental](https://lifecycle.r-lib.org/articles/figures/lifecycle-experimental.svg)](https://www.tidyverse.org/lifecycle/#experimental)
-
```r
library(RKorAPClient)
library(knitr)
-new("KorAPConnection", verbose = TRUE) %>%
+new("KorAPConnection", verbose = TRUE) %>% auth() %>%
collocationAnalysis(
"focus(in [tt/p=NN] {[tt/l=setzen]})",
leftContextSize = 1,
@@ -96,9 +94,17 @@
In order to perform collocation analysis and other textual queries on corpus parts for which KWIC access requires a login, you need to authorize your application with an access token.
-In the case of DeReKo, this can be done in two different ways.
+In the case of DeReKo, this can be done in three different ways.
-#### The old way: Authorize your RKorAPClient application manually
+#### 1. The latest and laziest way (currently only available in the [development version](#development-version) of RKorAPClient)
+
+Authorize your RKorAPClient application via the usual OAuth browser flow *using the default application id* and the `auth` method:
+
+```R
+kco <- new("KorAPConnection") %>% auth()
+```
+
+#### 2. The old way: Authorize your RKorAPClient application manually
1. Log in into the [KorAP DeReKo instance](https://korap.ids-mannheim.de/)
1. Open the [KorAP OAuth settings](https://korap.ids-mannheim.de/settings/oauth#page-top)
@@ -115,7 +121,10 @@
https://user-images.githubusercontent.com/11092081/142769056-b389649b-eac4-435f-ac6d-1715474a5605.mp4
-#### The new way (since March 2023)[^1]: Authorize your RKorAPClient application via the usual OAuth browser flow
+#### 3. The new way (since March 2023)[^1]
+
+Authorize your RKorAPClient application via the usual OAuth browser flow, using *your own application id* and the `auth` method:
+
[^1]: This new method has been made possible purely on the server side, so that it will also work with older versions of RKorAPClient.
@@ -123,24 +132,13 @@
2. Click on the copy symbol ⎘ behind the ID of your client application.
3. Paste your clipboard content overwriting `<application ID>` in the following example code:
```R
- library(httr)
-
- korap_app <- oauth_app("korap-client", key = "<application ID>", secret = NULL)
- korap_endpoint <- oauth_endpoint(NULL,
- "settings/oauth/authorize",
- "api/v1.0/oauth2/token",
- base_url = "https://korap.ids-mannheim.de")
- token_bundle = oauth2.0_token(korap_endpoint, korap_app, scope = "search match_info", cache = FALSE)
-
- kco <- new("KorAPConnection", accessToken = token_bundle[["credentials"]][["access_token"]])
+ kco <- new("KorAPConnection") %>% auth(app_id = "<application ID>")
```
-See also the [displayKwics demo](./demo/displayKwics.R).
-
-How to request access, only if no access token has been provided or persisted, is illustrated in the [gender variants demo](./demo/pluralGenderVariants.R) (try `demo("pluralGenderVariants")` ) and in the [adjective collocates demo](./demo/adjectiveCollocates.R) (try `demo("adjectiveCollocates")` ).
-
#### Storing and testing your authorized access
+(Not recommended for the default application id, as it is not secure.)
+
You can also persist the access token for subsequent sessions with the `persistAccessToken` function:
```R
@@ -184,6 +182,7 @@
# Arch Linux
pacman -S base-devel gcc-fortran libsodium curl
```
+
#### In RStudio
Start RStudio and click on *Install Packages…* in the *Tools* menu. Enter *RKorAPClient* in the *Packages* input field and click on the *Install* button (keeping *Install Dependencies* checked).
@@ -194,20 +193,21 @@
#### Or from the command line
+
Start R, then install RKorAPClient from CRAN (or development version from GitHub or KorAP's gerrit server).
##### CRAN version:
+
```r
install.packages("RKorAPClient")
```
-##### Development version (alternatives):
+##### Development version:
+
```r
-devtools::install_github("KorAP/RKorAPClient")
remotes::install_github("KorAP/RKorAPClient")
-devtools::install_git("https://korap.ids-mannheim.de/gerrit/KorAP/RKorAPClient")
-remotes::install_git("https://korap.ids-mannheim.de/gerrit/KorAP/RKorAPClient")
```
+
### Full installation videos
## Mac
diff --git a/man/KorAPConnection-class.Rd b/man/KorAPConnection-class.Rd
index 9230a42..363f97b 100644
--- a/man/KorAPConnection-class.Rd
+++ b/man/KorAPConnection-class.Rd
@@ -5,10 +5,6 @@
\alias{KorAPConnection-class}
\alias{KorAPConnection}
\alias{initialize,KorAPConnection-method}
-\alias{persistAccessToken,KorAPConnection-method}
-\alias{persistAccessToken}
-\alias{clearAccessToken,KorAPConnection-method}
-\alias{clearAccessToken}
\alias{apiCall,KorAPConnection-method}
\alias{apiCall}
\alias{clearCache,KorAPConnection-method}
@@ -28,10 +24,6 @@
cache = TRUE
)
-\S4method{persistAccessToken}{KorAPConnection}(kco, accessToken = kco@accessToken)
-
-\S4method{clearAccessToken}{KorAPConnection}(kco)
-
\S4method{apiCall}{KorAPConnection}(
kco,
url,
@@ -57,7 +49,9 @@
\item{accessToken}{OAuth2 access token. For queries on corpus parts with restricted
access (e.g. textual queries on IPR protected data), you need to authorize
your application with an access token.
-How to obtain an access token for the DeReKo KorAP instance is explained in the
+You can obtain an access token using the \code{\link[=auth]{auth()}} method.
+
+More details are explained in the
\href{https://github.com/KorAP/RKorAPClient#authorization}{authorization section}
of the RKorAPClient Readme on GitHub.
@@ -152,16 +146,4 @@
kq@collectedMatches$snippet
}
-\dontrun{
-
-kco <- new("KorAPConnection", accessToken="e739u6eOzkwADQPdVChxFg")
-persistAccessToken(kco)
-}
-
-\dontrun{
-
-kco <- new("KorAPConnection")
-clearAccessToken(kco)
-}
-
}
diff --git a/man/auth-KorAPConnection-method.Rd b/man/auth-KorAPConnection-method.Rd
new file mode 100644
index 0000000..8a1ab5d
--- /dev/null
+++ b/man/auth-KorAPConnection-method.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/KorAPConnection.R
+\name{auth,KorAPConnection-method}
+\alias{auth,KorAPConnection-method}
+\alias{auth}
+\title{Authorize RKorAPClient}
+\usage{
+\S4method{auth}{KorAPConnection}(kco, app_id = generic_kor_app_id, scope = "search match_info")
+}
+\arguments{
+\item{kco}{KorAPConnection object}
+
+\item{app_id}{OAuth2 application id. Defaults to the generic KorAP client application id.}
+
+\item{scope}{OAuth2 scope. Defaults to "search match_info".}
+}
+\value{
+KorAPConnection object with access token set in \verb{@accessToken}.
+}
+\description{
+\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
+
+Authorize RKorAPClient to make KorAP queries and download results on behalf of the user.
+}
+\examples{
+\dontrun{
+kco <- new("KorAPConnection", verbose = TRUE) \%>\% auth()
+df <- collocationAnalysis(kco, "focus([marmot/p=ADJA] {Ameisenplage})", leftContextSize=1, rightContextSize=0)
+}
+
+}
+\seealso{
+\code{\link[=persistAccessToken]{persistAccessToken()}}, \code{\link[=clearAccessToken]{clearAccessToken()}}
+}
diff --git a/man/clearAccessToken-KorAPConnection-method.Rd b/man/clearAccessToken-KorAPConnection-method.Rd
new file mode 100644
index 0000000..2dfa4b5
--- /dev/null
+++ b/man/clearAccessToken-KorAPConnection-method.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/KorAPConnection.R
+\name{clearAccessToken,KorAPConnection-method}
+\alias{clearAccessToken,KorAPConnection-method}
+\alias{clearAccessToken}
+\title{Clear access token from keyring and KorAPConnection object}
+\usage{
+\S4method{clearAccessToken}{KorAPConnection}(kco)
+}
+\arguments{
+\item{kco}{KorAPConnection object}
+}
+\value{
+KorAPConnection object with access token set to \code{NULL}.
+}
+\description{
+Clear access token from keyring and KorAPConnection object
+}
+\examples{
+
+\dontrun{
+kco <- new("KorAPConnection")
+kco <- clearAccessToken(kco)
+}
+
+}
+\seealso{
+\code{\link[=persistAccessToken]{persistAccessToken()}}
+}
diff --git a/man/persistAccessToken-KorAPConnection-method.Rd b/man/persistAccessToken-KorAPConnection-method.Rd
new file mode 100644
index 0000000..b66457e
--- /dev/null
+++ b/man/persistAccessToken-KorAPConnection-method.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/KorAPConnection.R
+\name{persistAccessToken,KorAPConnection-method}
+\alias{persistAccessToken,KorAPConnection-method}
+\alias{persistAccessToken}
+\title{Persist current access token in keyring}
+\usage{
+\S4method{persistAccessToken}{KorAPConnection}(kco, accessToken = kco@accessToken)
+}
+\arguments{
+\item{kco}{KorAPConnection object}
+
+\item{accessToken}{access token to be persisted. If not supplied, the current access token of the KorAPConnection object will be used.}
+}
+\value{
+KorAPConnection object.
+}
+\description{
+Persist current access token in keyring
+}
+\examples{
+\dontrun{
+kco <- new("KorAPConnection", accessToken="e739u6eOzkwADQPdVChxFg")
+persistAccessToken(kco)
+
+kco <- new("KorAPConnection") \%>\% auth(app_id="<my application id>") \%>\% persistAccessToken()
+}
+
+}
+\seealso{
+\code{\link[=clearAccessToken]{clearAccessToken()}}, \code{\link[=auth]{auth()}}
+}