Merge branch 'master' into git
This commit is contained in:
commit
ba2cb08904
268 changed files with 7008 additions and 1535 deletions
|
@ -9,7 +9,7 @@ from data import model
|
|||
from util.cache import cache_control_flask_restful
|
||||
|
||||
|
||||
def image_view(image, image_map):
|
||||
def image_view(image, image_map, include_locations=True, include_ancestors=True):
|
||||
extended_props = image
|
||||
if image.storage and image.storage.id:
|
||||
extended_props = image.storage
|
||||
|
@ -20,24 +20,35 @@ def image_view(image, image_map):
|
|||
if not aid or not aid in image_map:
|
||||
return ''
|
||||
|
||||
return image_map[aid]
|
||||
return image_map[aid].docker_image_id
|
||||
|
||||
# Calculate the ancestors string, with the DBID's replaced with the docker IDs.
|
||||
ancestors = [docker_id(a) for a in image.ancestors.split('/')]
|
||||
ancestors_string = '/'.join(ancestors)
|
||||
|
||||
return {
|
||||
image_data = {
|
||||
'id': image.docker_image_id,
|
||||
'created': format_date(extended_props.created),
|
||||
'comment': extended_props.comment,
|
||||
'command': json.loads(command) if command else None,
|
||||
'size': extended_props.image_size,
|
||||
'locations': list(image.storage.locations),
|
||||
'uploading': image.storage.uploading,
|
||||
'ancestors': ancestors_string,
|
||||
'sort_index': len(image.ancestors)
|
||||
'sort_index': len(image.ancestors),
|
||||
}
|
||||
|
||||
if include_locations:
|
||||
image_data['locations'] = list(image.storage.locations)
|
||||
|
||||
if include_ancestors:
|
||||
# Calculate the ancestors string, with the DBID's replaced with the docker IDs.
|
||||
ancestors = [docker_id(a) for a in image.ancestors.split('/')]
|
||||
image_data['ancestors'] = '/'.join(ancestors)
|
||||
|
||||
return image_data
|
||||
|
||||
|
||||
def historical_image_view(image, image_map):
|
||||
ancestors = [image_map[a] for a in image.ancestors.split('/')[1:-1]]
|
||||
normal_view = image_view(image, image_map)
|
||||
normal_view['history'] = [image_view(parent, image_map, False, False) for parent in ancestors]
|
||||
return normal_view
|
||||
|
||||
|
||||
@resource('/v1/repository/<repopath:repository>/image/')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
|
@ -62,7 +73,7 @@ class RepositoryImageList(RepositoryParamResource):
|
|||
filtered_images = []
|
||||
for image in all_images:
|
||||
if str(image.id) in found_image_ids:
|
||||
image_map[str(image.id)] = image.docker_image_id
|
||||
image_map[str(image.id)] = image
|
||||
filtered_images.append(image)
|
||||
|
||||
def add_tags(image_json):
|
||||
|
@ -90,9 +101,9 @@ class RepositoryImage(RepositoryParamResource):
|
|||
# Lookup all the ancestor images for the image.
|
||||
image_map = {}
|
||||
for current_image in model.get_parent_images(namespace, repository, image):
|
||||
image_map[str(current_image.id)] = image.docker_image_id
|
||||
image_map[str(current_image.id)] = current_image
|
||||
|
||||
return image_view(image, image_map)
|
||||
return historical_image_view(image, image_map)
|
||||
|
||||
|
||||
@resource('/v1/repository/<repopath:repository>/image/<image_id>/changes')
|
||||
|
|
|
@ -24,16 +24,22 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def org_view(o, teams):
|
||||
admin_org = AdministerOrganizationPermission(o.username)
|
||||
is_admin = admin_org.can()
|
||||
is_admin = AdministerOrganizationPermission(o.username).can()
|
||||
is_member = OrganizationMemberPermission(o.username).can()
|
||||
|
||||
view = {
|
||||
'name': o.username,
|
||||
'email': o.email if is_admin else '',
|
||||
'avatar': avatar.compute_hash(o.email, name=o.username),
|
||||
'teams': {t.name : team_view(o.username, t) for t in teams},
|
||||
'is_admin': is_admin
|
||||
'avatar': avatar.get_data_for_user(o),
|
||||
'is_admin': is_admin,
|
||||
'is_member': is_member
|
||||
}
|
||||
|
||||
if teams is not None:
|
||||
teams = sorted(teams, key=lambda team:team.id)
|
||||
view['teams'] = {t.name : team_view(o.username, t) for t in teams}
|
||||
view['ordered_teams'] = [team.name for team in teams]
|
||||
|
||||
if is_admin:
|
||||
view['invoice_email'] = o.invoice_email
|
||||
|
||||
|
@ -129,17 +135,17 @@ class Organization(ApiResource):
|
|||
@nickname('getOrganization')
|
||||
def get(self, orgname):
|
||||
""" Get the details for the specified organization """
|
||||
permission = OrganizationMemberPermission(orgname)
|
||||
if permission.can():
|
||||
try:
|
||||
org = model.get_organization(orgname)
|
||||
except model.InvalidOrganizationException:
|
||||
raise NotFound()
|
||||
try:
|
||||
org = model.get_organization(orgname)
|
||||
except model.InvalidOrganizationException:
|
||||
raise NotFound()
|
||||
|
||||
teams = None
|
||||
if OrganizationMemberPermission(orgname).can():
|
||||
teams = model.get_teams_within_org(org)
|
||||
return org_view(org, teams)
|
||||
|
||||
raise Unauthorized()
|
||||
return org_view(org, teams)
|
||||
|
||||
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('changeOrganizationDetails')
|
||||
|
@ -218,7 +224,7 @@ class OrgPrivateRepositories(ApiResource):
|
|||
@path_param('orgname', 'The name of the organization')
|
||||
class OrgnaizationMemberList(ApiResource):
|
||||
""" Resource for listing the members of an organization. """
|
||||
|
||||
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('getOrganizationMembers')
|
||||
def get(self, orgname):
|
||||
|
@ -297,16 +303,14 @@ class ApplicationInformation(ApiResource):
|
|||
if not application:
|
||||
raise NotFound()
|
||||
|
||||
org_hash = avatar.compute_hash(application.organization.email,
|
||||
name=application.organization.username)
|
||||
app_hash = (avatar.compute_hash(application.avatar_email, name=application.name) if
|
||||
application.avatar_email else org_hash)
|
||||
app_email = application.avatar_email or application.organization.email
|
||||
app_data = avatar.get_data(application.name, app_email, 'app')
|
||||
|
||||
return {
|
||||
'name': application.name,
|
||||
'description': application.description,
|
||||
'uri': application.application_uri,
|
||||
'avatar': app_hash,
|
||||
'avatar': app_data,
|
||||
'organization': org_view(application.organization, [])
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
|||
|
||||
from flask import request
|
||||
|
||||
from app import avatar
|
||||
from endpoints.api import (resource, nickname, require_repo_admin, RepositoryParamResource,
|
||||
log_action, request_error, validate_json_request, path_param)
|
||||
from data import model
|
||||
|
@ -16,7 +17,10 @@ def role_view(repo_perm_obj):
|
|||
}
|
||||
|
||||
def wrap_role_view_user(role_json, user):
|
||||
role_json['name'] = user.username
|
||||
role_json['is_robot'] = user.robot
|
||||
if not user.robot:
|
||||
role_json['avatar'] = avatar.get_data_for_user(user)
|
||||
return role_json
|
||||
|
||||
|
||||
|
@ -25,6 +29,12 @@ def wrap_role_view_org(role_json, user, org_members):
|
|||
return role_json
|
||||
|
||||
|
||||
def wrap_role_view_team(role_json, team):
|
||||
role_json['name'] = team.name
|
||||
role_json['avatar'] = avatar.get_data_for_team(team)
|
||||
return role_json
|
||||
|
||||
|
||||
@resource('/v1/repository/<repopath:repository>/permissions/team/')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
class RepositoryTeamPermissionList(RepositoryParamResource):
|
||||
|
@ -35,8 +45,11 @@ class RepositoryTeamPermissionList(RepositoryParamResource):
|
|||
""" List all team permission. """
|
||||
repo_perms = model.get_all_repo_teams(namespace, repository)
|
||||
|
||||
def wrapped_role_view(repo_perm):
|
||||
return wrap_role_view_team(role_view(repo_perm), repo_perm.team)
|
||||
|
||||
return {
|
||||
'permissions': {repo_perm.team.name: role_view(repo_perm)
|
||||
'permissions': {repo_perm.team.name: wrapped_role_view(repo_perm)
|
||||
for repo_perm in repo_perms}
|
||||
}
|
||||
|
||||
|
@ -232,7 +245,7 @@ class RepositoryTeamPermission(RepositoryParamResource):
|
|||
'role': new_permission['role']},
|
||||
repo=model.get_repository(namespace, repository))
|
||||
|
||||
return role_view(perm), 200
|
||||
return wrap_role_view_team(role_view(perm), perm.team), 200
|
||||
|
||||
@require_repo_admin
|
||||
@nickname('deleteTeamPermissions')
|
||||
|
|
|
@ -7,6 +7,7 @@ from auth.permissions import AdministerOrganizationPermission
|
|||
from auth.auth_context import get_authenticated_user
|
||||
from auth import scopes
|
||||
from data import model
|
||||
from app import avatar
|
||||
|
||||
|
||||
def prototype_view(proto, org_members):
|
||||
|
@ -16,6 +17,7 @@ def prototype_view(proto, org_members):
|
|||
'is_robot': user.robot,
|
||||
'kind': 'user',
|
||||
'is_org_member': user.robot or user.username in org_members,
|
||||
'avatar': avatar.get_data_for_user(user)
|
||||
}
|
||||
|
||||
if proto.delegate_user:
|
||||
|
@ -24,6 +26,7 @@ def prototype_view(proto, org_members):
|
|||
delegate_view = {
|
||||
'name': proto.delegate_team.name,
|
||||
'kind': 'team',
|
||||
'avatar': avatar.get_data_for_team(proto.delegate_team)
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -109,6 +109,8 @@ class RepositoryList(ApiResource):
|
|||
@query_param('sort', 'Whether to sort the results.', type=truthy_bool, default=False)
|
||||
@query_param('count', 'Whether to include a count of the total number of results available.',
|
||||
type=truthy_bool, default=False)
|
||||
@query_param('namespace_only', 'Whether to limit only to the given namespace.',
|
||||
type=truthy_bool, default=False)
|
||||
def get(self, args):
|
||||
"""Fetch the list of repositories under a variety of situations."""
|
||||
username = None
|
||||
|
@ -129,7 +131,8 @@ class RepositoryList(ApiResource):
|
|||
|
||||
repo_query = model.get_visible_repositories(username, limit=args['limit'], page=args['page'],
|
||||
include_public=args['public'], sort=args['sort'],
|
||||
namespace=args['namespace'])
|
||||
namespace=args['namespace'],
|
||||
namespace_only=args['namespace_only'])
|
||||
def repo_view(repo_obj):
|
||||
repo = {
|
||||
'namespace': repo_obj.namespace_user.username,
|
||||
|
|
|
@ -5,16 +5,63 @@ from auth.permissions import AdministerOrganizationPermission, OrganizationMembe
|
|||
from auth.auth_context import get_authenticated_user
|
||||
from auth import scopes
|
||||
from data import model
|
||||
from data.database import User, Team, Repository, FederatedLogin
|
||||
from util.names import format_robot_username
|
||||
|
||||
from flask import abort
|
||||
from app import avatar
|
||||
|
||||
def robot_view(name, token):
|
||||
return {
|
||||
'name': name,
|
||||
'token': token,
|
||||
'token': token
|
||||
}
|
||||
|
||||
|
||||
def permission_view(permission):
|
||||
return {
|
||||
'repository': {
|
||||
'name': permission.repository.name,
|
||||
'is_public': permission.repository.visibility.name == 'public'
|
||||
},
|
||||
'role': permission.role.name
|
||||
}
|
||||
|
||||
|
||||
def robots_list(prefix):
|
||||
tuples = model.list_entity_robot_permission_teams(prefix)
|
||||
|
||||
robots = {}
|
||||
robot_teams = set()
|
||||
|
||||
for robot_tuple in tuples:
|
||||
robot_name = robot_tuple.get(User.username)
|
||||
if not robot_name in robots:
|
||||
robots[robot_name] = {
|
||||
'name': robot_name,
|
||||
'token': robot_tuple.get(FederatedLogin.service_ident),
|
||||
'teams': [],
|
||||
'repositories': []
|
||||
}
|
||||
|
||||
team_name = robot_tuple.get(Team.name)
|
||||
repository_name = robot_tuple.get(Repository.name)
|
||||
|
||||
if team_name is not None:
|
||||
check_key = robot_name + ':' + team_name
|
||||
if not check_key in robot_teams:
|
||||
robot_teams.add(check_key)
|
||||
|
||||
robots[robot_name]['teams'].append({
|
||||
'name': team_name,
|
||||
'avatar': avatar.get_data(team_name, team_name, 'team')
|
||||
})
|
||||
|
||||
if repository_name is not None:
|
||||
if not repository_name in robots[robot_name]['repositories']:
|
||||
robots[robot_name]['repositories'].append(repository_name)
|
||||
|
||||
return {'robots': robots.values()}
|
||||
|
||||
@resource('/v1/user/robots')
|
||||
@internal_only
|
||||
class UserRobotList(ApiResource):
|
||||
|
@ -24,10 +71,7 @@ class UserRobotList(ApiResource):
|
|||
def get(self):
|
||||
""" List the available robots for the user. """
|
||||
user = get_authenticated_user()
|
||||
robots = model.list_entity_robot_tuples(user.username)
|
||||
return {
|
||||
'robots': [robot_view(name, password) for name, password in robots]
|
||||
}
|
||||
return robots_list(user.username)
|
||||
|
||||
|
||||
@resource('/v1/user/robots/<robot_shortname>')
|
||||
|
@ -73,10 +117,7 @@ class OrgRobotList(ApiResource):
|
|||
""" List the organization's robots. """
|
||||
permission = OrganizationMemberPermission(orgname)
|
||||
if permission.can():
|
||||
robots = model.list_entity_robot_tuples(orgname)
|
||||
return {
|
||||
'robots': [robot_view(name, password) for name, password in robots]
|
||||
}
|
||||
return robots_list(orgname)
|
||||
|
||||
raise Unauthorized()
|
||||
|
||||
|
@ -125,6 +166,47 @@ class OrgRobot(ApiResource):
|
|||
raise Unauthorized()
|
||||
|
||||
|
||||
@resource('/v1/user/robots/<robot_shortname>/permissions')
|
||||
@path_param('robot_shortname', 'The short name for the robot, without any user or organization prefix')
|
||||
@internal_only
|
||||
class UserRobotPermissions(ApiResource):
|
||||
""" Resource for listing the permissions a user's robot has in the system. """
|
||||
@require_user_admin
|
||||
@nickname('getUserRobotPermissions')
|
||||
def get(self, robot_shortname):
|
||||
""" Returns the list of repository permissions for the user's robot. """
|
||||
parent = get_authenticated_user()
|
||||
robot, password = model.get_robot(robot_shortname, parent)
|
||||
permissions = model.list_robot_permissions(robot.username)
|
||||
|
||||
return {
|
||||
'permissions': [permission_view(permission) for permission in permissions]
|
||||
}
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/robots/<robot_shortname>/permissions')
|
||||
@path_param('orgname', 'The name of the organization')
|
||||
@path_param('robot_shortname', 'The short name for the robot, without any user or organization prefix')
|
||||
@related_user_resource(UserRobotPermissions)
|
||||
class OrgRobotPermissions(ApiResource):
|
||||
""" Resource for listing the permissions an org's robot has in the system. """
|
||||
@require_user_admin
|
||||
@nickname('getOrgRobotPermissions')
|
||||
def get(self, orgname, robot_shortname):
|
||||
""" Returns the list of repository permissions for the org's robot. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
parent = model.get_organization(orgname)
|
||||
robot, password = model.get_robot(robot_shortname, parent)
|
||||
permissions = model.list_robot_permissions(robot.username)
|
||||
|
||||
return {
|
||||
'permissions': [permission_view(permission) for permission in permissions]
|
||||
}
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/user/robots/<robot_shortname>/regenerate')
|
||||
@path_param('robot_shortname', 'The short name for the robot, without any user or organization prefix')
|
||||
@internal_only
|
||||
|
|
|
@ -3,11 +3,15 @@ from endpoints.api import (ApiResource, parse_args, query_param, truthy_bool, ni
|
|||
from data import model
|
||||
from auth.permissions import (OrganizationMemberPermission, ViewTeamPermission,
|
||||
ReadRepositoryPermission, UserAdminPermission,
|
||||
AdministerOrganizationPermission)
|
||||
AdministerOrganizationPermission, ReadRepositoryPermission)
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from auth import scopes
|
||||
from app import avatar
|
||||
from app import avatar, get_app_url
|
||||
from operator import itemgetter
|
||||
from stringscore import liquidmetal
|
||||
from util.names import parse_robot_username
|
||||
|
||||
import math
|
||||
|
||||
@resource('/v1/entities/<prefix>')
|
||||
class EntitySearch(ApiResource):
|
||||
|
@ -45,7 +49,7 @@ class EntitySearch(ApiResource):
|
|||
'name': namespace_name,
|
||||
'kind': 'org',
|
||||
'is_org_member': True,
|
||||
'avatar': avatar.compute_hash(organization.email, name=organization.username),
|
||||
'avatar': avatar.get_data_for_org(organization),
|
||||
}]
|
||||
|
||||
except model.InvalidOrganizationException:
|
||||
|
@ -63,7 +67,8 @@ class EntitySearch(ApiResource):
|
|||
result = {
|
||||
'name': team.name,
|
||||
'kind': 'team',
|
||||
'is_org_member': True
|
||||
'is_org_member': True,
|
||||
'avatar': avatar.get_data_for_team(team)
|
||||
}
|
||||
return result
|
||||
|
||||
|
@ -71,11 +76,12 @@ class EntitySearch(ApiResource):
|
|||
user_json = {
|
||||
'name': user.username,
|
||||
'kind': 'user',
|
||||
'is_robot': user.is_robot,
|
||||
'is_robot': user.robot,
|
||||
'avatar': avatar.get_data_for_user(user)
|
||||
}
|
||||
|
||||
if organization is not None:
|
||||
user_json['is_org_member'] = user.is_robot or user.is_org_member
|
||||
user_json['is_org_member'] = user.robot or user.is_org_member
|
||||
|
||||
return user_json
|
||||
|
||||
|
@ -128,3 +134,154 @@ class FindRepositories(ApiResource):
|
|||
if (repo.visibility.name == 'public' or
|
||||
ReadRepositoryPermission(repo.namespace_user.username, repo.name).can())]
|
||||
}
|
||||
|
||||
|
||||
|
||||
def search_entity_view(username, entity):
|
||||
kind = 'user'
|
||||
avatar_data = avatar.get_data_for_user(entity)
|
||||
href = '/user/' + entity.username
|
||||
|
||||
if entity.organization:
|
||||
kind = 'organization'
|
||||
avatar_data = avatar.get_data_for_org(entity)
|
||||
href = '/organization/' + entity.username
|
||||
elif entity.robot:
|
||||
parts = parse_robot_username(entity.username)
|
||||
if parts[0] == username:
|
||||
href = '/user/' + username + '?tab=robots&showRobot=' + entity.username
|
||||
else:
|
||||
href = '/organization/' + parts[0] + '?tab=robots&showRobot=' + entity.username
|
||||
|
||||
kind = 'robot'
|
||||
avatar_data = None
|
||||
|
||||
return {
|
||||
'kind': kind,
|
||||
'avatar': avatar_data,
|
||||
'name': entity.username,
|
||||
'score': 1,
|
||||
'href': href
|
||||
}
|
||||
|
||||
|
||||
def conduct_team_search(username, query, encountered_teams, results):
|
||||
""" Finds the matching teams where the user is a member. """
|
||||
matching_teams = model.get_matching_user_teams(query, get_authenticated_user(), limit=5)
|
||||
for team in matching_teams:
|
||||
if team.id in encountered_teams:
|
||||
continue
|
||||
|
||||
encountered_teams.add(team.id)
|
||||
|
||||
results.append({
|
||||
'kind': 'team',
|
||||
'name': team.name,
|
||||
'organization': search_entity_view(username, team.organization),
|
||||
'avatar': avatar.get_data_for_team(team),
|
||||
'score': 2,
|
||||
'href': '/organization/' + team.organization.username + '/teams/' + team.name
|
||||
})
|
||||
|
||||
|
||||
def conduct_admined_team_search(username, query, encountered_teams, results):
|
||||
""" Finds matching teams in orgs admined by the user. """
|
||||
matching_teams = model.get_matching_admined_teams(query, get_authenticated_user(), limit=5)
|
||||
for team in matching_teams:
|
||||
if team.id in encountered_teams:
|
||||
continue
|
||||
|
||||
encountered_teams.add(team.id)
|
||||
|
||||
results.append({
|
||||
'kind': 'team',
|
||||
'name': team.name,
|
||||
'organization': search_entity_view(username, team.organization),
|
||||
'avatar': avatar.get_data_for_team(team),
|
||||
'score': 2,
|
||||
'href': '/organization/' + team.organization.username + '/teams/' + team.name
|
||||
})
|
||||
|
||||
|
||||
def conduct_repo_search(username, query, results):
|
||||
""" Finds matching repositories. """
|
||||
def can_read(repository):
|
||||
if repository.is_public:
|
||||
return True
|
||||
|
||||
return ReadRepositoryPermission(repository.namespace_user.username, repository.name).can()
|
||||
|
||||
only_public = username is None
|
||||
matching_repos = model.get_sorted_matching_repositories(query, only_public, can_read, limit=5)
|
||||
|
||||
for repo in matching_repos:
|
||||
repo_score = math.log(repo.count or 1, 10) or 1
|
||||
|
||||
# If the repository is under the user's namespace, give it 20% more weight.
|
||||
namespace = repo.namespace_user.username
|
||||
if OrganizationMemberPermission(namespace).can() or namespace == username:
|
||||
repo_score = repo_score * 1.2
|
||||
|
||||
results.append({
|
||||
'kind': 'repository',
|
||||
'namespace': search_entity_view(username, repo.namespace_user),
|
||||
'name': repo.name,
|
||||
'description': repo.description,
|
||||
'is_public': repo.is_public,
|
||||
'score': repo_score,
|
||||
'href': '/repository/' + repo.namespace_user.username + '/' + repo.name
|
||||
})
|
||||
|
||||
|
||||
def conduct_namespace_search(username, query, results):
|
||||
""" Finds matching users and organizations. """
|
||||
matching_entities = model.get_matching_user_namespaces(query, username, limit=5)
|
||||
for entity in matching_entities:
|
||||
results.append(search_entity_view(username, entity))
|
||||
|
||||
|
||||
def conduct_robot_search(username, query, results):
|
||||
""" Finds matching robot accounts. """
|
||||
matching_robots = model.get_matching_robots(query, username, limit=5)
|
||||
for robot in matching_robots:
|
||||
results.append(search_entity_view(username, robot))
|
||||
|
||||
|
||||
@resource('/v1/find/all')
|
||||
class ConductSearch(ApiResource):
|
||||
""" Resource for finding users, repositories, teams, etc. """
|
||||
@parse_args
|
||||
@query_param('query', 'The search query.', type=str, default='')
|
||||
@require_scope(scopes.READ_REPO)
|
||||
@nickname('conductSearch')
|
||||
def get(self, args):
|
||||
""" Get a list of entities and resources that match the specified query. """
|
||||
query = args['query']
|
||||
if not query:
|
||||
return {'results': []}
|
||||
|
||||
username = None
|
||||
results = []
|
||||
|
||||
if get_authenticated_user():
|
||||
username = get_authenticated_user().username
|
||||
|
||||
# Search for teams.
|
||||
encountered_teams = set()
|
||||
conduct_team_search(username, query, encountered_teams, results)
|
||||
conduct_admined_team_search(username, query, encountered_teams, results)
|
||||
|
||||
# Search for robot accounts.
|
||||
conduct_robot_search(username, query, results)
|
||||
|
||||
# Search for repos.
|
||||
conduct_repo_search(username, query, results)
|
||||
|
||||
# Search for users and orgs.
|
||||
conduct_namespace_search(username, query, results)
|
||||
|
||||
# Modify the results' scores via how close the query term is to each result's name.
|
||||
for result in results:
|
||||
result['score'] = result['score'] * liquidmetal.score(result['name'], query)
|
||||
|
||||
return {'results': sorted(results, key=itemgetter('score'), reverse=True)}
|
||||
|
|
|
@ -15,6 +15,7 @@ from auth.permissions import SuperUserPermission
|
|||
from auth.auth_context import get_authenticated_user
|
||||
from data.database import User
|
||||
from util.config.configutil import add_enterprise_config_defaults
|
||||
from util.config.provider import CannotWriteConfigException
|
||||
from util.config.validator import validate_service_for_config, SSL_FILENAMES
|
||||
from data.runmigration import run_alembic_migration
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ def user_view(user):
|
|||
'username': user.username,
|
||||
'email': user.email,
|
||||
'verified': user.verified,
|
||||
'avatar': avatar.compute_hash(user.email, name=user.username),
|
||||
'avatar': avatar.get_data_for_user(user),
|
||||
'super_user': superusers.is_superuser(user.username)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,45 @@
|
|||
from flask import request
|
||||
from flask import request, abort
|
||||
|
||||
from endpoints.api import (resource, nickname, require_repo_read, require_repo_write,
|
||||
RepositoryParamResource, log_action, NotFound, validate_json_request,
|
||||
path_param)
|
||||
path_param, format_date)
|
||||
from endpoints.api.image import image_view
|
||||
from data import model
|
||||
from auth.auth_context import get_authenticated_user
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
|
||||
@resource('/v1/repository/<repopath:repository>/tag/')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
class ListRepositoryTags(RepositoryParamResource):
|
||||
""" Resource for listing repository tags. """
|
||||
|
||||
@require_repo_write
|
||||
@nickname('listRepoTags')
|
||||
def get(self, namespace, repository):
|
||||
repo = model.get_repository(namespace, repository)
|
||||
if not repo:
|
||||
abort(404)
|
||||
|
||||
def tag_view(tag):
|
||||
tag_info = {
|
||||
'name': tag.name,
|
||||
'docker_image_id': tag.image.docker_image_id,
|
||||
}
|
||||
|
||||
if tag.lifetime_start_ts > 0:
|
||||
tag_info['start_ts'] = tag.lifetime_start_ts
|
||||
|
||||
if tag.lifetime_end_ts > 0:
|
||||
tag_info['end_ts'] = tag.lifetime_end_ts
|
||||
|
||||
return tag_info
|
||||
|
||||
tags = model.list_repository_tag_history(repo, limit=100)
|
||||
return {'tags': [tag_view(tag) for tag in tags]}
|
||||
|
||||
|
||||
@resource('/v1/repository/<repopath:repository>/tag/<tag>')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
|
@ -92,7 +125,7 @@ class RepositoryTagImages(RepositoryParamResource):
|
|||
parent_images = model.get_parent_images(namespace, repository, tag_image)
|
||||
image_map = {}
|
||||
for image in parent_images:
|
||||
image_map[str(image.id)] = image.docker_image_id
|
||||
image_map[str(image.id)] = image
|
||||
|
||||
parents = list(parent_images)
|
||||
parents.reverse()
|
||||
|
|
|
@ -52,11 +52,11 @@ def team_view(orgname, team):
|
|||
view_permission = ViewTeamPermission(orgname, team.name)
|
||||
role = model.get_team_org_role(team).name
|
||||
return {
|
||||
'id': team.id,
|
||||
'name': team.name,
|
||||
'description': team.description,
|
||||
'can_view': view_permission.can(),
|
||||
'role': role
|
||||
'role': role,
|
||||
'avatar': avatar.get_data_for_team(team)
|
||||
}
|
||||
|
||||
def member_view(member, invited=False):
|
||||
|
@ -64,7 +64,7 @@ def member_view(member, invited=False):
|
|||
'name': member.username,
|
||||
'kind': 'user',
|
||||
'is_robot': member.robot,
|
||||
'avatar': avatar.compute_hash(member.email, name=member.username) if not member.robot else None,
|
||||
'avatar': avatar.get_data_for_user(member),
|
||||
'invited': invited,
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ def invite_view(invite):
|
|||
return {
|
||||
'email': invite.email,
|
||||
'kind': 'invite',
|
||||
'avatar': avatar.compute_hash(invite.email),
|
||||
'avatar': avatar.get_data(invite.email, invite.email, 'user'),
|
||||
'invited': True
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import logging
|
||||
import json
|
||||
|
||||
from flask import request
|
||||
from random import SystemRandom
|
||||
from flask import request, abort
|
||||
from flask.ext.login import logout_user
|
||||
from flask.ext.principal import identity_changed, AnonymousIdentity
|
||||
from peewee import IntegrityError
|
||||
|
@ -35,7 +36,7 @@ def user_view(user):
|
|||
admin_org = AdministerOrganizationPermission(o.username)
|
||||
return {
|
||||
'name': o.username,
|
||||
'avatar': avatar.compute_hash(o.email, name=o.username),
|
||||
'avatar': avatar.get_data_for_org(o),
|
||||
'is_org_admin': admin_org.can(),
|
||||
'can_create_repo': admin_org.can() or CreateRepositoryPermission(o.username).can(),
|
||||
'preferred_namespace': not (o.stripe_id is None)
|
||||
|
@ -58,15 +59,16 @@ def user_view(user):
|
|||
logins = model.list_federated_logins(user)
|
||||
|
||||
user_response = {
|
||||
'verified': user.verified,
|
||||
'anonymous': False,
|
||||
'username': user.username,
|
||||
'avatar': avatar.compute_hash(user.email, name=user.username),
|
||||
'avatar': avatar.get_data_for_user(user)
|
||||
}
|
||||
|
||||
user_admin = UserAdminPermission(user.username)
|
||||
if user_admin.can():
|
||||
user_response.update({
|
||||
'is_me': True,
|
||||
'verified': user.verified,
|
||||
'email': user.email,
|
||||
'organizations': [org_view(o) for o in organizations],
|
||||
'logins': [login_view(login) for login in logins],
|
||||
|
@ -76,7 +78,7 @@ def user_view(user):
|
|||
'tag_expiration': user.removed_tag_expiration_s,
|
||||
})
|
||||
|
||||
if features.SUPER_USERS:
|
||||
if features.SUPER_USERS and SuperUserPermission().can():
|
||||
user_response.update({
|
||||
'super_user': user and user == get_authenticated_user() and SuperUserPermission().can()
|
||||
})
|
||||
|
@ -175,8 +177,8 @@ class User(ApiResource):
|
|||
'description': 'The user\'s email address',
|
||||
},
|
||||
'avatar': {
|
||||
'type': 'string',
|
||||
'description': 'Avatar hash representing the user\'s icon'
|
||||
'type': 'object',
|
||||
'description': 'Avatar data representing the user\'s icon'
|
||||
},
|
||||
'organizations': {
|
||||
'type': 'array',
|
||||
|
@ -224,8 +226,13 @@ class User(ApiResource):
|
|||
if 'password' in user_data:
|
||||
logger.debug('Changing password for user: %s', user.username)
|
||||
log_action('account_change_password', user.username)
|
||||
|
||||
# Change the user's password.
|
||||
model.change_password(user, user_data['password'])
|
||||
|
||||
# Login again to reset their session cookie.
|
||||
common_login(user)
|
||||
|
||||
if features.MAILING:
|
||||
send_password_changed(user.username, user.email)
|
||||
|
||||
|
@ -335,13 +342,51 @@ class PrivateRepositories(ApiResource):
|
|||
}
|
||||
|
||||
|
||||
@resource('/v1/user/clientkey')
|
||||
@internal_only
|
||||
class ClientKey(ApiResource):
|
||||
""" Operations for returning an encrypted key which can be used in place of a password
|
||||
for the Docker client. """
|
||||
schemas = {
|
||||
'GenerateClientKey': {
|
||||
'id': 'GenerateClientKey',
|
||||
'type': 'object',
|
||||
'required': [
|
||||
'password',
|
||||
],
|
||||
'properties': {
|
||||
'password': {
|
||||
'type': 'string',
|
||||
'description': 'The user\'s password',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('generateUserClientKey')
|
||||
@validate_json_request('GenerateClientKey')
|
||||
def post(self):
|
||||
""" Return's the user's private client key. """
|
||||
username = get_authenticated_user().username
|
||||
password = request.get_json()['password']
|
||||
|
||||
(result, error_message) = authentication.verify_user(username, password)
|
||||
if not result:
|
||||
raise request_error(message=error_message)
|
||||
|
||||
return {
|
||||
'key': authentication.encrypt_user_password(password)
|
||||
}
|
||||
|
||||
|
||||
def conduct_signin(username_or_email, password):
|
||||
needs_email_verification = False
|
||||
invalid_credentials = False
|
||||
|
||||
verified = None
|
||||
try:
|
||||
verified = authentication.verify_user(username_or_email, password)
|
||||
(verified, error_message) = authentication.verify_user(username_or_email, password)
|
||||
except model.TooManyUsersException as ex:
|
||||
raise license_error(exception=ex)
|
||||
|
||||
|
@ -407,7 +452,7 @@ class ConvertToOrganization(ApiResource):
|
|||
|
||||
# Ensure that the sign in credentials work.
|
||||
admin_password = convert_data['adminPassword']
|
||||
admin_user = authentication.verify_user(admin_username, admin_password)
|
||||
(admin_user, error_message) = authentication.verify_user(admin_username, admin_password)
|
||||
if not admin_user:
|
||||
raise request_error(reason='invaliduser',
|
||||
message='The admin user credentials are not valid')
|
||||
|
@ -621,17 +666,16 @@ class UserNotification(ApiResource):
|
|||
|
||||
def authorization_view(access_token):
|
||||
oauth_app = access_token.application
|
||||
app_email = oauth_app.avatar_email or oauth_app.organization.email
|
||||
return {
|
||||
'application': {
|
||||
'name': oauth_app.name,
|
||||
'description': oauth_app.description,
|
||||
'url': oauth_app.application_uri,
|
||||
'avatar': avatar.compute_hash(oauth_app.avatar_email or oauth_app.organization.email,
|
||||
name=oauth_app.name),
|
||||
'avatar': avatar.get_data(oauth_app.name, app_email, 'app'),
|
||||
'organization': {
|
||||
'name': oauth_app.organization.username,
|
||||
'avatar': avatar.compute_hash(oauth_app.organization.email,
|
||||
name=oauth_app.organization.username)
|
||||
'avatar': avatar.get_data_for_org(oauth_app.organization)
|
||||
}
|
||||
},
|
||||
'scopes': scopes.get_scope_information(access_token.scope),
|
||||
|
@ -745,6 +789,7 @@ class StarredRepositoryList(ApiResource):
|
|||
'repository': repository,
|
||||
}, 201
|
||||
|
||||
|
||||
@resource('/v1/user/starred/<repopath:repository>')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
class StarredRepository(RepositoryParamResource):
|
||||
|
@ -759,3 +804,17 @@ class StarredRepository(RepositoryParamResource):
|
|||
if repo:
|
||||
model.unstar_repository(user, repo)
|
||||
return 'Deleted', 204
|
||||
|
||||
|
||||
@resource('/v1/users/<username>')
|
||||
class Users(ApiResource):
|
||||
""" Operations related to retrieving information about other users. """
|
||||
@nickname('getUserInformation')
|
||||
def get(self, username):
|
||||
""" Get user information for the specified user. """
|
||||
user = model.get_user(username)
|
||||
if user is None or user.organization or user.robot:
|
||||
abort(404)
|
||||
|
||||
return user_view(user)
|
||||
|
||||
|
|
Reference in a new issue