[#8450] make /rest/auth/tools endpoint, refactor repo fields, remove old/confusing tool fields
diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index e2e34db..d40668e 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -786,15 +786,15 @@
Returns dict that will be included in project's API under tools key.
"""
- return {
+ json = {
'name': self.config.tool_name,
'mount_point': self.config.options.mount_point,
- 'url': self.config.url(),
- 'icons': self.icons,
- 'installable': self.installable,
- 'tool_label': self.tool_label,
+ 'url': h.absurl(self.config.url()),
'mount_label': self.config.options.mount_label
}
+ if self.api_root:
+ json['api_url'] = h.absurl('/rest' + self.config.url())
+ return json
def get_attachment_export_path(self, path='', *args):
return os.path.join(path, self.config.options.mount_point, *args)
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index ac0c7b4..e5154f0 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -554,6 +554,21 @@
redirect('/')
+class AuthRestController:
+
+ @expose('json:')
+ def tools(self, tool_type: str):
+ apps = []
+ for p in c.user.my_projects():
+ for ac in p.app_configs:
+ if ac.tool_name == tool_type and h.has_access(ac, 'read'):
+ apps.append(p.app_instance(ac))
+
+ return {
+ 'tools': apps, # TG automatically runs their __json__ methods
+ }
+
+
def select_new_primary_addr(user, ignore_emails=[]):
for obj_e in user.email_addresses:
obj = user.address_object(obj_e)
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index 1f76940..ab98ec1 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -313,6 +313,11 @@
def index(self, **kw):
app: Application = c.app
repo: M.Repository = app.repo
+
+ # core fields, shared in other API endpoints
+ resp = app.__json__()
+
+ # more expensive fields that we only show in this individual API endpoint
try:
all_commits = repo._impl.new_commits(all_commits=True)
except Exception:
@@ -320,16 +325,8 @@
commit_count = None
else:
commit_count = len(all_commits)
- resp = dict(
- commit_count=commit_count,
- name=app.config.options.mount_label,
- type=app.tool_label,
- )
- for clone_cat in repo.clone_command_categories(anon=c.user.is_anonymous()):
- respkey = 'clone_url_' + clone_cat['key']
- resp[respkey] = repo.clone_url(clone_cat['key'],
- username='' if c.user.is_anonymous() else c.user.username,
- )
+ resp['commit_count'] = commit_count
+
return resp
@expose('json:')
diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index c36b454..5bf4c78 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -31,6 +31,7 @@
from ming.orm import session
from allura import model as M
+from allura.controllers.auth import AuthRestController
from allura.lib import helpers as h
from allura.lib import security
from allura.lib import plugin
@@ -48,6 +49,7 @@
def __init__(self):
self.oauth = OAuthNegotiator()
+ self.auth = AuthRestController()
def _check_security(self):
if not request.path.startswith('/rest/oauth/'): # everything but OAuthNegotiator
diff --git a/Allura/allura/lib/repository.py b/Allura/allura/lib/repository.py
index 24789f9..8444191 100644
--- a/Allura/allura/lib/repository.py
+++ b/Allura/allura/lib/repository.py
@@ -250,6 +250,16 @@
def uninstall(self, project):
allura.tasks.repo_tasks.uninstall.post()
+ def __json__(self):
+ data = super().__json__()
+ repo: M.Repository = self.repo
+ for clone_cat in repo.clone_command_categories(anon=c.user.is_anonymous()):
+ respkey = 'clone_url_' + clone_cat['key']
+ data[respkey] = repo.clone_url(clone_cat['key'],
+ username='' if c.user.is_anonymous() else c.user.username,
+ )
+ return data
+
class RepoAdminController(DefaultAdminController):
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index ae4b0b4..915b887 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -15,6 +15,8 @@
# specific language governing permissions and limitations
# under the License.
+from __future__ import annotations
+
import logging
import calendar
import typing
@@ -56,6 +58,7 @@
if typing.TYPE_CHECKING:
from ming.odm.mapper import Query
+ from allura.model.project import Project
log = logging.getLogger(__name__)
@@ -768,9 +771,9 @@
def script_name(self):
return '/u/' + self.username + '/'
- def my_projects(self):
+ def my_projects(self) -> typing.Iterable[Project]:
if self.is_anonymous():
- return
+ return []
roles = g.credentials.user_roles(user_id=self._id)
# filter out projects to which the user belongs to no named groups (i.e., role['roles'] is empty)
projects = [r['project_id'] for r in roles if r['roles']]
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 0e35ae3..d384b5c 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -15,9 +15,12 @@
# specific language governing permissions and limitations
# under the License.
+from __future__ import annotations
+
import logging
from calendar import timegm
from collections import Counter, OrderedDict
+from collections.abc import Iterable
from hashlib import sha256
import typing
from datetime import datetime
@@ -67,6 +70,7 @@
if typing.TYPE_CHECKING:
from ming.odm.mapper import Query
+ from allura.model import AppConfig
log = logging.getLogger(__name__)
@@ -250,7 +254,7 @@
acl = FieldProperty(ACL(permissions=_perms_init))
neighborhood_invitations = FieldProperty([S.ObjectId])
neighborhood = RelationProperty(Neighborhood)
- app_configs = RelationProperty('AppConfig')
+ app_configs: Iterable[AppConfig] = RelationProperty('AppConfig')
category_id = FieldProperty(S.ObjectId, if_missing=None)
deleted = FieldProperty(bool, if_missing=False)
labels = FieldProperty([str])
@@ -899,7 +903,7 @@
app.install(self)
return app
- def uninstall_app(self, mount_point):
+ def uninstall_app(self, mount_point: str):
app = self.app_instance(mount_point)
if app is None:
return
@@ -908,7 +912,7 @@
with h.push_config(c, project=self, app=app):
app.uninstall(self)
- def app_instance(self, mount_point_or_config):
+ def app_instance(self, mount_point_or_config: AppConfig | str):
if isinstance(mount_point_or_config, AppConfig):
app_config = mount_point_or_config
else:
@@ -921,12 +925,12 @@
else:
return App(self, app_config)
- def app_config(self, mount_point):
+ def app_config(self, mount_point: str):
return AppConfig.query.find({
'project_id': self._id,
'options.mount_point': mount_point}).first()
- def app_config_by_tool_type(self, tool_type):
+ def app_config_by_tool_type(self, tool_type: str):
for ac in self.app_configs:
if ac.tool_name == tool_type:
return ac
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index 0c2239c..7ebbffd 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -365,7 +365,6 @@
fs_path = FieldProperty(str)
url_path = FieldProperty(str)
status = FieldProperty(str)
- email_address = ''
additional_viewable_extensions = FieldProperty(str)
heads = FieldProperty(S.Deprecated)
branches = FieldProperty(S.Deprecated)
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index e555cf8..7766ece 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -45,7 +45,7 @@
from allura.tests import TestController
from allura.tests import decorators as td
from allura.tests.decorators import audits, out_audits
-from alluratest.controller import setup_trove_categories
+from alluratest.controller import setup_trove_categories, TestRestApiBase
from allura import model as M
from allura.lib import plugin
from allura.lib import helpers as h
@@ -1138,6 +1138,43 @@
assert_not_equal(r.content_length, 777)
+class TestAuthRest(TestRestApiBase):
+
+ def test_tools_list_anon(self):
+ resp = self.api_get('/rest/auth/tools/wiki', user='*anonymous')
+ assert_equal(resp.json, {
+ 'tools': []
+ })
+
+ def test_tools_list_invalid_tool(self):
+ resp = self.api_get('/rest/auth/tools/af732q9547235')
+ assert_equal(resp.json, {
+ 'tools': []
+ })
+
+ @td.with_tool('test', 'Wiki', mount_point='docs', mount_label='Documentation')
+ def test_tools_list_wiki(self):
+ resp = self.api_get('/rest/auth/tools/wiki')
+ assert_equal(resp.json, {
+ 'tools': [
+ {
+ 'mount_label': 'Wiki',
+ 'mount_point': 'wiki',
+ 'name': 'wiki',
+ 'url': 'http://localhost/adobe/wiki/',
+ 'api_url': 'http://localhost/rest/adobe/wiki/',
+ },
+ {
+ 'mount_label': 'Documentation',
+ 'mount_point': 'docs',
+ 'name': 'wiki',
+ 'url': 'http://localhost/p/test/docs/',
+ 'api_url': 'http://localhost/rest/p/test/docs/',
+ },
+ ]
+ })
+
+
class TestPreferences(TestController):
@td.with_user_project('test-admin')
def test_personal_data(self):
diff --git a/Allura/docs/api-rest/api.raml b/Allura/docs/api-rest/api.raml
index 8167cc4..8574205 100755
--- a/Allura/docs/api-rest/api.raml
+++ b/Allura/docs/api-rest/api.raml
@@ -17,7 +17,9 @@
# under the License.
#
#
-# http://apiworkbench.com/ is a useful tool to edit this file
+# https://github.com/mulesoft/api-designer is a useful tool to edit this file
+# web version http://mulesoft.github.io/api-designer/
+# version that works well with local files: https://github.com/sichvoge/api-designer-fs but you must change git:// to https:// in its package.json before `npm install`
---
title: Apache Allura
version: 1
@@ -38,6 +40,25 @@
- title: API Overview
content: !include docs.md
+/auth:
+ description: |
+ Authorization related APIs. See also OAuth
+
+ /tools/{tool_type}:
+ uriParameters:
+ tool_type:
+ type: string
+ example: git
+ type: {
+ generic: {
+ example: !include examples/auth-tools.json,
+ schema: !include schemas/auth-tools.json
+ }
+ }
+ get:
+ description: |
+ List tools (e.g. "git" repos) that the current user is associated with
+
/oauth:
description: |
See separate docs section for authenticating with the OAuth 1.0 APIs
@@ -263,7 +284,7 @@
description: ticket description
type: string
required: false
- ticket_form.assigned_to::
+ ticket_form.assigned_to:
type: string
required: false
description: username of ticket assignee
@@ -311,7 +332,7 @@
description: ticket description
type: string
required: false
- ticket_form.assigned_to::
+ ticket_form.assigned_to:
type: string
required: false
description: username of ticket assignee
diff --git a/Allura/docs/api-rest/examples/auth-tools.json b/Allura/docs/api-rest/examples/auth-tools.json
new file mode 100755
index 0000000..cdd569f
--- /dev/null
+++ b/Allura/docs/api-rest/examples/auth-tools.json
@@ -0,0 +1,22 @@
+{
+ "tools": [
+ {
+ "url": "https://forge-allura.apache.org/p/test/code/",
+ "api_url": "https://forge-allura.apache.org/rest/p/test/code/",
+ "mount_label": "Code",
+ "mount_point": "code",
+ "name": "git",
+ "clone_url_https_anon": "https://forge-allura.apache.org/git/p/test/code/",
+ "clone_url_ro": "git://forge-allura.apache.org/git/p/test/code"
+ },
+ {
+ "url": "https://forge-allura.apache.org/u/someuser/widgets-fork/",
+ "api_url": "https://forge-allura.apache.org/rest/u/someuser/widgets-fork/",
+ "mount_label": "Widgets - Fork",
+ "mount_point": "widgets-fork",
+ "name": "git",
+ "clone_url_https_anon": "https://forge-allura.apache.org/git/u/someuser/widgets-fork",
+ "clone_url_ro": "git://forge-allura.apache.org/git/u/someuser/widgets-fork"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Allura/docs/api-rest/examples/scm.json b/Allura/docs/api-rest/examples/scm.json
index a5a2f4b..7718a39 100755
--- a/Allura/docs/api-rest/examples/scm.json
+++ b/Allura/docs/api-rest/examples/scm.json
@@ -1,6 +1,9 @@
{
- "name": "Code",
- "type": "Git",
+ "url": "https://forge-allura.apache.org/p/test/code/",
+ "api_url": "https://forge-allura.apache.org/rest/p/test/code/",
+ "mount_label": "Code",
+ "mount_point": "code",
+ "name": "git",
"commit_count": 17,
"clone_url_https_anon": "https://forge-allura.apache.org/git/p/test/code",
"clone_url_ro": "git://forge-allura.apache.org/git/p/test/code"
diff --git a/Allura/docs/api-rest/schemas/auth-tools.json b/Allura/docs/api-rest/schemas/auth-tools.json
new file mode 100755
index 0000000..5de2ae5
--- /dev/null
+++ b/Allura/docs/api-rest/schemas/auth-tools.json
@@ -0,0 +1,50 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema",
+ "type": "object",
+ "id": "#",
+ "properties": {
+ "tools": {
+ "items": {
+ "type": "object",
+ "id": "0",
+ "properties": {
+ "url": {
+ "type": "string",
+ "id": "url"
+ },
+ "api_url": {
+ "type": "string",
+ "id": "api_url"
+ },
+ "mount_label": {
+ "type": "string",
+ "id": "mount_label"
+ },
+ "mount_point": {
+ "type": "string",
+ "id": "mount_point"
+ },
+ "name": {
+ "type": "string",
+ "id": "name"
+ },
+ "clone_url_https_anon": {
+ "type": "string",
+ "id": "clone_url_https_anon"
+ },
+ "clone_url_ro": {
+ "type": "string",
+ "id": "clone_url_ro"
+ },
+ "clone_url_*": {
+ "type": "string",
+ "id": "clone_url_*"
+ }
+ }
+ },
+ "type": "array",
+ "id": "tools"
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/Allura/docs/api-rest/schemas/page.json b/Allura/docs/api-rest/schemas/page.json
index d961145..c54240e 100755
--- a/Allura/docs/api-rest/schemas/page.json
+++ b/Allura/docs/api-rest/schemas/page.json
@@ -4,7 +4,7 @@
"properties": {
"related_artifacts": {
"items": {
- "type": ["null", "string"],
+ "type": ["null", "string"]
},
"type": ["null", "array"],
"id": "related_artifacts"
diff --git a/Allura/docs/api-rest/schemas/scm.json b/Allura/docs/api-rest/schemas/scm.json
index 0a69b65..b686e79 100755
--- a/Allura/docs/api-rest/schemas/scm.json
+++ b/Allura/docs/api-rest/schemas/scm.json
@@ -3,29 +3,41 @@
"type": "object",
"id": "#",
"properties": {
+ "url": {
+ "type": "string",
+ "id": "url"
+ },
+ "api_url": {
+ "type": "string",
+ "id": "api_url"
+ },
+ "mount_label": {
+ "type": "string",
+ "id": "mount_label"
+ },
+ "mount_point": {
+ "type": "string",
+ "id": "mount_point"
+ },
"name": {
"type": "string",
"id": "name"
},
- "type": {
- "type": "string",
- "id": "type"
- },
"commit_count": {
"type": "integer",
"id": "commit_count"
},
"clone_url_https_anon": {
"type": "string",
- "id": "name"
+ "id": "clone_url_https_anon"
},
"clone_url_ro": {
"type": "string",
- "id": "name"
+ "id": "clone_url_ro"
},
"clone_url_*": {
"type": "string",
- "id": "name"
+ "id": "clone_url_*"
}
}
}
diff --git a/Allura/docs/api-rest/schemas/tickets.json b/Allura/docs/api-rest/schemas/tickets.json
index 6a68c2c..a30e01c 100755
--- a/Allura/docs/api-rest/schemas/tickets.json
+++ b/Allura/docs/api-rest/schemas/tickets.json
@@ -46,7 +46,7 @@
},
"default": {
"id": "default",
- "type": ""
+ "type": "string"
},
"description": {
"id": "description",
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index f4854db..2bc49ed 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -556,10 +556,13 @@
def test_index(self):
resp = self.app.get('/rest/p/test/src-git/', status=200)
assert_equal(resp.json, {
+ 'api_url': 'http://localhost/rest/p/test/src-git/',
+ 'url': 'http://localhost/p/test/src-git/',
+ 'mount_label': 'Git',
+ 'mount_point': 'src-git',
+ 'name': 'git',
+ 'clone_url_file': '/srv/git/p/test/testgit', # should be "src-git" but test data is weird?
'commit_count': 5,
- 'name': 'Git',
- 'type': 'Git',
- 'clone_url_file': '/srv/git/p/test/testgit',
})
def test_commits(self):