This is an automated email from the ASF dual-hosted git repository. kentontaylor pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/allura.git
commit e96c19fd58a33e7c3e91ca1f80694961649cd750 Author: Dave Brondsema <[email protected]> AuthorDate: Fri Jul 29 13:19:46 2022 -0400 [#8450] make /rest/auth/tools endpoint, refactor repo fields, remove old/confusing tool fields --- Allura/allura/app.py | 10 ++--- Allura/allura/controllers/auth.py | 15 +++++++ Allura/allura/controllers/repository.py | 17 +++----- Allura/allura/controllers/rest.py | 2 + Allura/allura/lib/repository.py | 10 +++++ Allura/allura/model/auth.py | 7 ++- Allura/allura/model/project.py | 14 +++--- Allura/allura/model/repository.py | 1 - Allura/allura/tests/functional/test_auth.py | 39 ++++++++++++++++- Allura/docs/api-rest/api.raml | 27 ++++++++++-- Allura/docs/api-rest/examples/auth-tools.json | 22 ++++++++++ Allura/docs/api-rest/examples/scm.json | 7 ++- Allura/docs/api-rest/schemas/auth-tools.json | 50 ++++++++++++++++++++++ Allura/docs/api-rest/schemas/page.json | 2 +- Allura/docs/api-rest/schemas/scm.json | 26 ++++++++--- Allura/docs/api-rest/schemas/tickets.json | 2 +- .../forgegit/tests/functional/test_controllers.py | 9 ++-- 17 files changed, 219 insertions(+), 41 deletions(-) diff --git a/Allura/allura/app.py b/Allura/allura/app.py index e2e34db0d..d40668e1b 100644 --- a/Allura/allura/app.py +++ b/Allura/allura/app.py @@ -786,15 +786,15 @@ class Application: 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 ac0c7b400..e5154f05a 100644 --- a/Allura/allura/controllers/auth.py +++ b/Allura/allura/controllers/auth.py @@ -554,6 +554,21 @@ class AuthController(BaseController): 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 1f769408a..ab98ec1ef 100644 --- a/Allura/allura/controllers/repository.py +++ b/Allura/allura/controllers/repository.py @@ -313,6 +313,11 @@ class RepoRestController(RepoRootController, AppRestControllerMixin): 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 @@ class RepoRestController(RepoRootController, AppRestControllerMixin): 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 c36b45451..5bf4c786c 100644 --- a/Allura/allura/controllers/rest.py +++ b/Allura/allura/controllers/rest.py @@ -31,6 +31,7 @@ import colander 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 @@ class RestController: 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 24789f971..84441912d 100644 --- a/Allura/allura/lib/repository.py +++ b/Allura/allura/lib/repository.py @@ -250,6 +250,16 @@ class RepositoryApp(Application): 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 ae4b0b402..915b88736 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 @@ from .timeline import ActivityNode, ActivityObject if typing.TYPE_CHECKING: from ming.odm.mapper import Query + from allura.model.project import Project log = logging.getLogger(__name__) @@ -768,9 +771,9 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable): 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 0e35ae309..d384b5c01 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 @@ import six if typing.TYPE_CHECKING: from ming.odm.mapper import Query + from allura.model import AppConfig log = logging.getLogger(__name__) @@ -250,7 +254,7 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject): 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 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject): 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 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject): 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 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject): 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 0c2239ccc..7ebbffdb7 100644 --- a/Allura/allura/model/repository.py +++ b/Allura/allura/model/repository.py @@ -365,7 +365,6 @@ class Repository(Artifact, ActivityObject): 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 e555cf8de..7766ece84 100644 --- a/Allura/allura/tests/functional/test_auth.py +++ b/Allura/allura/tests/functional/test_auth.py @@ -45,7 +45,7 @@ import oauth2 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 @@ class TestAuth(TestController): 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 8167cc437..85742059d 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 @@ documentation: - 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 @@ documentation: 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 @@ documentation: 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 000000000..cdd569fcf --- /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 a5a2f4b4e..7718a397f 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 000000000..5de2ae579 --- /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 d9611450b..c54240e3e 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 0a69b65e7..b686e796a 100755 --- a/Allura/docs/api-rest/schemas/scm.json +++ b/Allura/docs/api-rest/schemas/scm.json @@ -3,13 +3,25 @@ "type": "object", "id": "#", "properties": { - "name": { + "url": { "type": "string", - "id": "name" + "id": "url" + }, + "api_url": { + "type": "string", + "id": "api_url" }, - "type": { + "mount_label": { "type": "string", - "id": "type" + "id": "mount_label" + }, + "mount_point": { + "type": "string", + "id": "mount_point" + }, + "name": { + "type": "string", + "id": "name" }, "commit_count": { "type": "integer", @@ -17,15 +29,15 @@ }, "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 6a68c2cd8..a30e01c74 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 f4854db25..2bc49ed68 100644 --- a/ForgeGit/forgegit/tests/functional/test_controllers.py +++ b/ForgeGit/forgegit/tests/functional/test_controllers.py @@ -556,10 +556,13 @@ class TestRestController(_TestCase): 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):
