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()}}
+}