You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by gc...@apache.org on 2024/06/03 16:14:13 UTC

(allura) branch master updated (7321fb9b6 -> f6c97a8f2)

This is an automated email from the ASF dual-hosted git repository.

gcruz pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git


    from 7321fb9b6 fix username hover on ticket search results
     new ca980de86 [#7272] do not allow http: redirect urls
     new 36b22194a [#7272] better repr for if/when oauthlib DEBUG logging is enabled
     new f6c97a8f2 [#7272] do not permit tokens in URL for oauth2

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 Allura/allura/controllers/rest.py           | 10 ++++++++++
 Allura/allura/lib/validators.py             | 10 ++++++++++
 Allura/allura/lib/widgets/oauth_widgets.py  | 18 ++++++++++++------
 Allura/allura/model/oauth.py                |  6 ++++++
 Allura/allura/tests/functional/test_rest.py |  3 +++
 Allura/docs/api-rest/docs.md                | 17 ++++++++---------
 Allura/docs/api-rest/securitySchemes.yaml   |  4 ++--
 Allura/docs/api-rest/traits.yaml            |  3 ---
 scripts/new_ticket.py                       |  3 +--
 scripts/project_export                      |  6 +++---
 10 files changed, 55 insertions(+), 25 deletions(-)


(allura) 02/03: [#7272] better repr for if/when oauthlib DEBUG logging is enabled

Posted by gc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gcruz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 36b22194aab858f0e9cbefe18483f2b2346299cc
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Wed May 29 14:46:35 2024 -0400

    [#7272] better repr for if/when oauthlib DEBUG logging is enabled
---
 Allura/allura/controllers/rest.py | 5 +++++
 Allura/allura/model/oauth.py      | 6 ++++++
 2 files changed, 11 insertions(+)

diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index ec1a3266f..9cb8ca180 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -255,6 +255,11 @@ class Oauth1Validator(oauthlib.oauth1.RequestValidator):
 
 
 class Oauth2Validator(oauthlib.oauth2.RequestValidator):
+    def __init__(self):
+        super().__init__()
+        if asbool(tg.config.get('debug')):
+            oauthlib.set_debug(True)
+
     def validate_client_id(self, client_id: str, request: oauthlib.common.Request, *args, **kwargs) -> bool:
         return M.OAuth2ClientApp.query.get(client_id=client_id) is not None
 
diff --git a/Allura/allura/model/oauth.py b/Allura/allura/model/oauth.py
index 69043635a..d70b5baaa 100644
--- a/Allura/allura/model/oauth.py
+++ b/Allura/allura/model/oauth.py
@@ -192,6 +192,9 @@ class OAuth2ClientApp(MappedClass):
     def description_html(self):
         return g.markdown.cached_convert(self, 'description')
 
+    def __repr__(self):
+        return f'<{self.__class__.__name__} _id={self._id} client_id={self.client_id} {self.name}>'
+
 
 class OAuth2AuthorizationCode(MappedClass):
     class __mongometa__:
@@ -250,6 +253,9 @@ class OAuth2AccessToken(MappedClass):
 
     user = RelationProperty('User')
 
+    def __repr__(self):
+        return f'<{self.__class__.__name__} _id={self._id} client_id={self.client_id} user_id={self.user_id}>'
+
 
 def dummy_oauths():
     from allura.controllers.rest import Oauth1Validator


(allura) 03/03: [#7272] do not permit tokens in URL for oauth2

Posted by gc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gcruz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git

commit f6c97a8f2507a93ed74eb24c53a1458f056a08d6
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Wed May 29 15:46:21 2024 -0400

    [#7272] do not permit tokens in URL for oauth2
---
 Allura/allura/controllers/rest.py           |  3 +++
 Allura/allura/tests/functional/test_rest.py |  3 +++
 Allura/docs/api-rest/docs.md                | 17 ++++++++---------
 Allura/docs/api-rest/securitySchemes.yaml   |  4 ++--
 Allura/docs/api-rest/traits.yaml            |  3 ---
 scripts/new_ticket.py                       |  3 +--
 scripts/project_export                      |  6 +++---
 7 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index 9cb8ca180..b42605eca 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -80,6 +80,9 @@ class RestController:
                 if not self._is_oauth2_enabled():
                     raise
 
+                if params_auth:
+                    raise exc.HTTPUnauthorized('Do not use tokens in URL, instead use header Authorization: Bearer $TOKEN')
+
                 access_token = self.oauth2._authenticate()
                 if not access_token:
                     raise
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index d3b896b8e..21d260254 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -443,6 +443,9 @@ class TestRestHome(TestRestApiBase):
         r = self.api_post('/rest/p/test/wiki/Home', params=params)
         assert r.status_int == 200
 
+        r = self.app.get('/rest/p/test/wiki/Home', params={'access_token': token.access_token}, status=401)
+        assert b'Do not use tokens in URL' in r.body
+
     @td.with_wiki
     @mock.patch.dict(config, {'auth.oauth2.enabled': True})
     def test_oauth2_expired_token_authentication(self):
diff --git a/Allura/docs/api-rest/docs.md b/Allura/docs/api-rest/docs.md
index 35db1ae64..dfd25c1db 100755
--- a/Allura/docs/api-rest/docs.md
+++ b/Allura/docs/api-rest/docs.md
@@ -41,20 +41,18 @@ The following tools have API support:
 
 In order to use the API for authenticated actions, you should use the OAuth account page to create a consumer key for your application.  Once you have a consumer key, you must have a site user (e.g. your own account, if you're writing a single script) authorize your application to act on his or her behalf.
 
-You can also use your normal browser session as authentication for the API.  This is useful for manually testing API calls or for in-browser applications (such as extensions or user-scripts).  It is not recommended for programatic access, however, as it would require you to store your account username and password, and thus cannot be easily managed or revoked.
+You can also use your normal browser session as authentication for the API.  This is useful for manually testing API calls or for in-browser applications (such as extensions or user-scripts).  It is not recommended for programmatic access, however, as it would require you to store your account username and password, and thus cannot be easily managed or revoked.
 
 Without authentication, all API requests have the permissions of an anonymous visitor.  To view or change anything that requires a login, you must authenticate to the API using OAuth.  You must first register for an OAuth consumer token at <https://forge-allura.apache.org/auth/oauth/>.  Once you have registered, you will be be able to see your consumer key and consumer secret, or generate a bearer token, at <https://forge-allura.apache.org/auth/oauth/>.
 
 
 ### OAuth With Bearer Tokens
 
-The easiest way to use the API with your own account is to use a bearer token.  Once you have generated a bearer token at <https://forge-allura.apache.org.net/auth/oauth/>, you just include it in the request to the API via the `access_token` URL parameter, `access_token` POST form field, or http header like `Authorization: Bearer MY_BEARER_TOKEN`.
-
-Note, however, that to use bearer tokens, you *must* use HTTPS/SSL for the request.
+The easiest way to use the API with your own account is to use a bearer token.  Once you have generated a bearer token at <https://forge-allura.apache.org.net/auth/oauth/>, you just include it in the request to the API via a http header like `Authorization: Bearer MY_BEARER_TOKEN`.
 
 Simple URL example to access a private ticket:
 
-https://forge-allura.apache.org/rest/p/allura/tickets/35/?access_token=MY_BEARER_TOKEN
+curl -H 'Authorization: Bearer MY_BEARER_TOKEN' https://forge-allura.apache.org/rest/p/allura/tickets/35/
 
 Python code example to create a new ticket:
 
@@ -63,8 +61,9 @@ Python code example to create a new ticket:
 
     BEARER_TOKEN = '<bearer token from oauth page>'
 
-    r = requests.post('https://forge-allura.apache.org/rest/p/test-project/tickets/new', params={
-            'access_token': BEARER_TOKEN,
+    r = requests.post('https://forge-allura.apache.org/rest/p/test-project/tickets/new',
+          headers={'Authorization': f'Bearer {BEARER_TOKEN}'}
+          params={
             'ticket_form.summary': 'Test ticket',
             'ticket_form.description': 'This is a test ticket',
             'ticket_form.labels': 'test',
@@ -156,7 +155,7 @@ You can then use your access token with the REST API.  For instance script to cr
     print("Done.  Response was:")
     print(response)
 
-### OAuth2 Authorization
+### OAuth2 Authorization (Third-Party Apps)
 
 Another option for authorizing your apps is to use the OAuth2 workflow. This is accomplished by authorizing the application which generates an `authorization_code` that can be later exchanged for an `access_token`
 
@@ -290,4 +289,4 @@ It requires `user` and `perm` parameters and will return JSON dict with `result`
 
 Project information is available in DOAP format with additional custom RDF fields at /rest/p/{project}?doap
 
-This is separate from the normal JSON API at /rest/p/{project}
\ No newline at end of file
+This is separate from the normal JSON API at /rest/p/{project}
diff --git a/Allura/docs/api-rest/securitySchemes.yaml b/Allura/docs/api-rest/securitySchemes.yaml
index 60a56f4e2..a5484cd6f 100755
--- a/Allura/docs/api-rest/securitySchemes.yaml
+++ b/Allura/docs/api-rest/securitySchemes.yaml
@@ -39,7 +39,7 @@
 
 - oauth_2_0:
     description: |
-        OAuth 2.0 may also be used to authenticate API requests.
+        OAuth 2.0 may be used to authenticate API requests.
 
         First authorize your application at https://forge-allura.apache.org/auth/oauth2/authorize with following
         query string parameters:
@@ -64,7 +64,7 @@
 
         Use the access token in an HTTP header like:
 
-        `Authorization: Bearer MY_BEARER_TOKEN``
+        `Authorization: Bearer MY_BEARER_TOKEN`
     type: OAuth 2.0
     settings:
       authorizationUri: https://forge-allura.apache.org/auth/oauth2/authorize
diff --git a/Allura/docs/api-rest/traits.yaml b/Allura/docs/api-rest/traits.yaml
index d3f17a4f7..9e1b9170f 100755
--- a/Allura/docs/api-rest/traits.yaml
+++ b/Allura/docs/api-rest/traits.yaml
@@ -73,9 +73,6 @@
         required: true
         description: The username to check
 - bearerAuth:
-    # This can also be done with ?access_token=
-    # But it's simpler in the docs / Try It interface to just show one option
-    # and using the header method keeps it separate from query params that the individual API endpoints use
     headers:
       Authorization:
         description: |
diff --git a/scripts/new_ticket.py b/scripts/new_ticket.py
index 6dbf5cc73..3f67ff976 100755
--- a/scripts/new_ticket.py
+++ b/scripts/new_ticket.py
@@ -47,8 +47,7 @@ if __name__ == '__main__':
     description = sys.stdin.read()
     print('-----------------------------------------------')
 
-    r = requests.post(opts.url, params={
-        'access_token': access_token,
+    r = requests.post(opts.url, headers={'Authorization': f'Bearer {access_token}'}, params={
         'ticket_form.summary': summary,
         'ticket_form.description': description,
     }, timeout=30)
diff --git a/scripts/project_export b/scripts/project_export
index 160688000..9cea96605 100755
--- a/scripts/project_export
+++ b/scripts/project_export
@@ -96,11 +96,11 @@ $VERBOSE && curl_s=''
 URL="https://$HOST/rest/p/$PROJECT/admin/export"
 
 function json() {
-    python -c "import sys, json;  print json.load(sys.stdin)['$1']"
+    python3 -c "import sys, json;  print(json.load(sys.stdin)['$1'])"
 }
 
 $VERBOSE && echo "Posting to $URL"
-curl $curl_s -D $HEADERS -o $BODY --data "access_token=$ACCESS_TOKEN&tools=$TOOLS" "$URL" || error "Running curl failed"
+curl $curl_s -H "Authorization: Bearer $ACCESS_TOKEN" -D $HEADERS -o $BODY --data "tools=$TOOLS" "$URL" || error "Running curl failed"
 head -n1 $HEADERS | grep 400 && error "Invalid or missing tool"
 head -n1 $HEADERS | grep 503 && error "Export already in progress"
 head -n1 $HEADERS | grep -v 200 && error "Error: $(head -n1 $HEADERS)"
@@ -110,7 +110,7 @@ filename=`cat $BODY | json filename`
 minutes=0
 while true; do
     $VERBOSE && echo "Checking $URL_status"
-    curl $curl_s -D $HEADERS -o $BODY "${URL}_status?access_token=$ACCESS_TOKEN" || error "Running curl failed"
+    curl $curl_s -H "Authorization: Bearer $ACCESS_TOKEN" -D $HEADERS -o $BODY "${URL}_status" || error "Running curl failed"
     head -n1 $HEADERS | grep -v 200 && error "Error: $(head -n1 $HEADERS)"
     status=`cat $BODY | json status`
     $VERBOSE && echo "Status: $status"


(allura) 01/03: [#7272] do not allow http: redirect urls

Posted by gc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gcruz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git

commit ca980de86cab8fdd08817156dd00505c635db49d
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Wed May 29 14:40:07 2024 -0400

    [#7272] do not allow http: redirect urls
---
 Allura/allura/controllers/rest.py          |  2 ++
 Allura/allura/lib/validators.py            | 10 ++++++++++
 Allura/allura/lib/widgets/oauth_widgets.py | 18 ++++++++++++------
 3 files changed, 24 insertions(+), 6 deletions(-)

diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index bfa95a4bf..ec1a3266f 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -259,6 +259,8 @@ class Oauth2Validator(oauthlib.oauth2.RequestValidator):
         return M.OAuth2ClientApp.query.get(client_id=client_id) is not None
 
     def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
+        if redirect_uri.startswith('http:'):
+            return False
         client = M.OAuth2ClientApp.query.get(client_id=client_id)
         return redirect_uri in client.redirect_uris
 
diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py
index 2f2776b39..8d6556c24 100644
--- a/Allura/allura/lib/validators.py
+++ b/Allura/allura/lib/validators.py
@@ -84,6 +84,16 @@ class NonHttpUrl(URL):
     ''', re.I | re.VERBOSE)
 
 
+class HttpsUrl(URL):
+    add_http = False
+
+    def _convert_to_python(self, value, state):
+        value = super()._convert_to_python(value, state)
+        if not value.startswith('https://'):
+            raise fev.Invalid("Must be https://", value, state)
+        return value
+
+
 class UnicodeString(fev.UnicodeString):
     """
     Override UnicodeString to fix bytes handling.
diff --git a/Allura/allura/lib/widgets/oauth_widgets.py b/Allura/allura/lib/widgets/oauth_widgets.py
index 5c7105c1f..ba314c3d0 100644
--- a/Allura/allura/lib/widgets/oauth_widgets.py
+++ b/Allura/allura/lib/widgets/oauth_widgets.py
@@ -63,17 +63,23 @@ class OAuth2ApplicationForm(ForgeForm):
 
         # SortableRepeatedField would be nice to use (and ignore sorting) so you can add many dynamically,
         # but couldn't get it to work easily
+
+        # use HttpsUrl so unencrypted http is prevented and tokens can't be intercepted
+        # in theory could allow some other protocols (but not http:) so it can work with mobile apps etc
         redirect_url_1 = ew.TextField(
             label='Redirect URL(s)',
-            validator=fev.URL(not_empty=True),
-            attrs=dict(type='url', style='min-width:25em', required=True),
+            validator=V.HttpsUrl(not_empty=True),
+            attrs=dict(type='url', style='min-width:25em', required=True, placeholder='https://...',
+                       pattern='https://.*', title='must start with https://'),
         )
         redirect_url_2 = ew.TextField(
-            validator=fev.URL(),
-            attrs=dict(type='url', style='min-width:25em; margin-left: 162px;'),  # match grid-4 label width
+            validator=V.HttpsUrl(),
+            attrs=dict(type='url', style='min-width:25em; margin-left: 162px;',  # match grid-4 label width
+                       pattern='https://.*', title='must start with https://'),
         )
         redirect_url_3 = ew.TextField(
-            validator=fev.URL(),
-            attrs=dict(type='url', style='min-width:25em; margin-left: 162px;'),  # match grid-4 label width
+            validator=V.HttpsUrl(),
+            attrs=dict(type='url', style='min-width:25em; margin-left: 162px;',  # match grid-4 label width
+                       pattern='https://.*', title='must start with https://'),
         )