[#8461] update oauth lib in docs & wiki-copy.py examples
diff --git a/Allura/docs/api-rest/docs.md b/Allura/docs/api-rest/docs.md
index fb2b605..425ae89 100755
--- a/Allura/docs/api-rest/docs.md
+++ b/Allura/docs/api-rest/docs.md
@@ -82,32 +82,24 @@
 ### OAuth 1.0 Application Authorization (Third-Party Apps)
 
 
-If you want your application to be able to use the API on behalf of another user, that user must authorize your application to act on their behalf.  This is usually accomplished by obtaining a request token and directing the user authorize the request.  The following is an example of how one would authorize an application in Python using the python-oauth2 library.  First, run `pip install oauth2` and `pip install certifi`.
+If you want your application to be able to use the API on behalf of another user, that user must authorize your application to act on their behalf.  This is usually accomplished by obtaining a request token and directing the user authorize the request.  The following is an example of how one would authorize an application in Python using the requests_oauthlib library.  First, run `pip install requests_oauthlib`
 
-    import oauth2 as oauth  # misleading package name, oauth2 implements OAuth 1.0 spec
-    import certifi
-    from urllib.parse import parse_qs, parse_qsl, urlencode
+    from requests_oauthlib import OAuth1Session
     import webbrowser
 
     CONSUMER_KEY = '<consumer key from registration>'
     CONSUMER_SECRET = '<consumer secret from registration>'
-    REQUEST_TOKEN_URL = 'https://sourceforge.net/rest/oauth/request_token'
-    AUTHORIZE_URL = 'https://sourceforge.net/rest/oauth/authorize'
-    ACCESS_TOKEN_URL = 'https://sourceforge.net/rest/oauth/access_token'
+    REQUEST_TOKEN_URL = 'https://forge-allura.apache.org/rest/oauth/request_token'
+    AUTHORIZE_URL = 'https://forge-allura.apache.org/rest/oauth/authorize'
+    ACCESS_TOKEN_URL = 'https://forge-allura.apache.org/rest/oauth/access_token'
     
-    consumer = oauth.Consumer(CONSUMER_KEY, CONSUMER_SECRET)
-    client = oauth.Client(consumer)
-    client.ca_certs = certifi.where()
+    oauth = OAuth1Session(CONSUMER_KEY, client_secret=CONSUMER_SECRET)
     
     # Step 1: Get a request token. This is a temporary token that is used for 
     # having the user authorize an access token and to sign the request to obtain 
     # said access token.
     
-    resp, content = client.request(REQUEST_TOKEN_URL, 'GET')
-    if resp['status'] != '200':
-        raise Exception("Invalid response %s." % resp['status'])
-    
-    request_token = dict(parse_qsl(content.decode('utf-8')))
+    request_token = oauth.fetch_request_token(REQUEST_TOKEN_URL)
     
     # these are intermediate tokens and not needed later
     # print("Request Token:")
@@ -119,7 +111,7 @@
     # redirect. In a web application you would redirect the user to the URL
     # below, specifying the additional parameter oauth_callback=<your callback URL>.
     
-    webbrowser.open("%s?oauth_token=%s" % (AUTHORIZE_URL, request_token['oauth_token']))
+    webbrowser.open(oauth.authorization_url(AUTHORIZE_URL, request_token['oauth_token']))
     
     # Since we didn't specify a callback, the user must now enter the PIN displayed in 
     # their browser.  If you had specified a callback URL, it would have been called with 
@@ -131,13 +123,7 @@
     # request token to sign this request. After this is done you throw away the
     # request token and use the access token returned. You should store this 
     # access token somewhere safe, like a database, for future use.
-    token = oauth.Token(request_token[b'oauth_token'].decode(), request_token[b'oauth_token_secret'].decode())
-    token.set_verifier(oauth_verifier)
-    client = oauth.Client(consumer, token)
-    client.ca_certs = certifi.where()
-    
-    resp, content = client.request(ACCESS_TOKEN_URL, "GET")
-    access_token = dict(parse_qsl(content.decode('utf-8')))
+    access_token = oauth.fetch_access_token(ACCESS_TOKEN_URL, oauth_verifier)
     
     print("Access Token:")
     print("    - oauth_token        = %s" % access_token['oauth_token'])
@@ -149,10 +135,7 @@
 
 You can then use your access token with the REST API.  For instance script to create a wiki page might look like this:
 
-    from urllib.parse import urlparse, parse_qsl, urlencode
-
-    import oauth2 as oauth
-    import certifi
+    from requests_oauthlib import OAuth1Session
     
     PROJECT='test'
     
@@ -162,17 +145,14 @@
     ACCESS_KEY='<access key from previous script>'
     ACCESS_SECRET='<access secret from previous script>'
     
-    URL_BASE='https://sourceforge.net/rest/'
+    URL_BASE='https://forge-allura.apache.org/rest/'
     
-    consumer = oauth.Consumer(CONSUMER_KEY, CONSUMER_SECRET)
-    access_token = oauth.Token(ACCESS_KEY, ACCESS_SECRET)
-    client = oauth.Client(consumer, access_token)
-    client.ca_certs = certifi.where()
+    oauth = OAuth1Session(CONSUMER_KEY, client_secret=CONSUMER_SECRET,
+                          resource_owner_key=ACCESS_KEY, resource_owner_secret=ACCESS_SECRET)
     
-    response = client.request(
-        URL_BASE + 'p/' + PROJECT + '/wiki/TestPage', 'POST',
-        body=urlencode(dict(
-                text='This is a test page')))
+    response = oauth.post(URL_BASE + 'p/' + PROJECT + '/wiki/TestPage',
+                          data=dict(text='This is a test page'))
+    response.raise_for_status()
     print("Done.  Response was:")
     print(response)
 
diff --git a/scripts/wiki-copy.py b/scripts/wiki-copy.py
index 0f80a34..fc65537 100644
--- a/scripts/wiki-copy.py
+++ b/scripts/wiki-copy.py
@@ -19,16 +19,12 @@
 
 import os
 import sys
-import six.moves.urllib.request
-import six.moves.urllib.parse
-import six.moves.urllib.error
-import six.moves.urllib.parse
 from optparse import OptionParser
-import json
-
-from six.moves.configparser import ConfigParser, NoOptionError
+from configparser import ConfigParser, NoOptionError
 import webbrowser
-import oauth2 as oauth
+
+import requests
+from requests_oauthlib import OAuth1Session
 
 
 def main():
@@ -45,32 +41,29 @@
     base_url = options.to_wiki.split('/rest/')[0]
     oauth_client = make_oauth_client(base_url)
 
-    wiki_data = six.moves.urllib.request.urlopen(options.from_wiki).read()
-    wiki_json = json.loads(wiki_data)['pages']
+    wiki_json = requests.get(options.from_wiki).json()['pages']
     for p in wiki_json:
-        from_url = options.from_wiki + six.moves.urllib.parse.quote(p)
-        to_url = options.to_wiki + six.moves.urllib.parse.quote(p)
+        from_url = options.from_wiki.rstrip('/') + '/' + p
+        to_url = options.to_wiki.rstrip('/') + '/' + p
         try:
-            page_data = six.moves.urllib.request.urlopen(from_url).read()
-            page_json = json.loads(page_data)
+            page_json = requests.get(from_url).json()
             if options.debug:
                 print(page_json['text'])
                 break
-            resp = oauth_client.request(
-                to_url, 'POST', body=six.moves.urllib.parse.urlencode(dict(text=page_json['text'].encode('utf-8'))))
-            if resp[0]['status'] == '200':
+            resp = oauth_client.post(to_url, data=dict(text=page_json['text']))
+            if resp.status_code == 200:
                 print("Posted {} to {}".format(page_json['title'], to_url))
             else:
-                print("Error posting {} to {}: {} (project may not exist)".format(page_json['title'], to_url, resp[0]['status']))
+                print("Error posting {} to {}: {} (project may not exist)".format(page_json['title'], to_url, resp.status_code))
                 break
         except Exception:
             print("Error processing " + p)
             raise
 
 
-def make_oauth_client(base_url):
+def make_oauth_client(base_url) -> requests.Session:
     """
-    Build an oauth.Client with which callers can query Allura.
+    Build an oauth client with which callers can query Allura.
     """
     config_file = os.path.join(os.environ['HOME'], '.allurarc')
     cp = ConfigParser()
@@ -80,49 +73,38 @@
     AUTHORIZE_URL = base_url + '/rest/oauth/authorize'
     ACCESS_TOKEN_URL = base_url + '/rest/oauth/access_token'
     oauth_key = option(cp, base_url, 'oauth_key',
-                       'Forge API OAuth Key (%s/auth/oauth/): ' % base_url)
+                       'Forge API OAuth Consumer Key (%s/auth/oauth/): ' % base_url)
     oauth_secret = option(cp, base_url, 'oauth_secret',
-                          'Forge API Oauth Secret: ')
-    consumer = oauth.Consumer(oauth_key, oauth_secret)
+                          'Forge API Oauth Consumer Secret: ')
 
     try:
         oauth_token = cp.get(base_url, 'oauth_token')
         oauth_token_secret = cp.get(base_url, 'oauth_token_secret')
     except NoOptionError:
-        client = oauth.Client(consumer)
-        resp, content = client.request(REQUEST_TOKEN_URL, 'GET')
-        assert resp['status'] == '200', resp
-
-        request_token = dict(six.moves.urllib.parse.parse_qsl(content))
-        pin_url = "{}?oauth_token={}".format(
-            AUTHORIZE_URL, request_token['oauth_token'])
-        if getattr(webbrowser.get(), 'name', '') == 'links':
-            # sandboxes
+        oauthSess = OAuth1Session(oauth_key, client_secret=oauth_secret)
+        request_token = oauthSess.fetch_request_token(REQUEST_TOKEN_URL)
+        pin_url = oauthSess.authorization_url(AUTHORIZE_URL, request_token['oauth_token'])
+        if isinstance(webbrowser.get(), webbrowser.GenericBrowser):
             print("Go to %s" % pin_url)
         else:
             webbrowser.open(pin_url)
         oauth_verifier = input('What is the PIN? ')
-
-        token = oauth.Token(
-            request_token['oauth_token'], request_token['oauth_token_secret'])
-        token.set_verifier(oauth_verifier)
-        client = oauth.Client(consumer, token)
-        resp, content = client.request(ACCESS_TOKEN_URL, "GET")
-        access_token = dict(six.moves.urllib.parse.parse_qsl(content))
+        access_token = oauthSess.fetch_access_token(ACCESS_TOKEN_URL, oauth_verifier)
         oauth_token = access_token['oauth_token']
         oauth_token_secret = access_token['oauth_token_secret']
 
         cp.set(base_url, 'oauth_token', oauth_token)
         cp.set(base_url, 'oauth_token_secret', oauth_token_secret)
+        # save oauth token for later use
+        cp.write(open(config_file, 'w'))
+        print(f'Saving oauth tokens in {config_file} for later re-use')
+        print()
 
-    # save oauth token for later use
-    cp.write(open(config_file, 'w'))
-    print(f'Saving oauth tokens in {config_file} for later re-use')
-    print()
+    else:
+        oauthSess = OAuth1Session(oauth_key, client_secret=oauth_secret,
+                                  resource_owner_key=oauth_token, resource_owner_secret=oauth_token_secret)
 
-    access_token = oauth.Token(oauth_token, oauth_token_secret)
-    oauth_client = oauth.Client(consumer, access_token)
-    return oauth_client
+    return oauthSess
 
 
 def option(cp, section, key, prompt=None):