mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-29 13:45:20 +01:00
Merge pull request #4483 from stonezdj/api4assign_role_to_group4
Add REST API for assign role to group
This commit is contained in:
commit
df63a73fd4
@ -627,6 +627,167 @@ paths:
|
|||||||
description: Project ID does not exist.
|
description: Project ID does not exist.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
'/projects/{project_id}/projectmembers':
|
||||||
|
get:
|
||||||
|
summary: Get all project member information
|
||||||
|
description: Get all project member information
|
||||||
|
parameters:
|
||||||
|
- name: project_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Relevant project ID.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Get project members successfully.
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/ProjectMemberEntity'
|
||||||
|
'400':
|
||||||
|
description: The project id is invalid.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: User in session does not have permission to the project.
|
||||||
|
'404':
|
||||||
|
description: Project ID does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
post:
|
||||||
|
summary: Create project member
|
||||||
|
description: >-
|
||||||
|
Create project member relationship, the member can be one of the user_member and group_member,
|
||||||
|
The user_member need to specify user_id or username. If the user already exist in harbor DB, specify the user_id,
|
||||||
|
If does not exist in harbor DB, it will SearchAndOnBoard the user.
|
||||||
|
The group_member need to specify id or ldap_group_dn. If the group already exist in harbor DB. specify the user group's id,
|
||||||
|
If does not exist, it will SearchAndOnBoard the group.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: project_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Relevant project ID.
|
||||||
|
- name: project_member
|
||||||
|
in: body
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ProjectMember'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Project member created successfully.
|
||||||
|
'400':
|
||||||
|
description: Illegal format of project member or project id is invalid.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: User in session does not have permission to the project.
|
||||||
|
'404':
|
||||||
|
description: Project does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
'/projects/{project_id}/projectmembers/{mid}':
|
||||||
|
get:
|
||||||
|
summary: Get the project member information
|
||||||
|
description: Get the project member information
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: project_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Relevant project ID.
|
||||||
|
- name: mid
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: The member ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Project member retrieved successfully.
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ProjectMemberEntity'
|
||||||
|
'400':
|
||||||
|
description: Illegal format of project member or invalid project id, member id.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: User in session does not have permission to the project.
|
||||||
|
'404':
|
||||||
|
description: Project or projet member does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
put:
|
||||||
|
summary: Update project member
|
||||||
|
description: Update project member relationship
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: project_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Relevant project ID.
|
||||||
|
- name: mid
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Member ID.
|
||||||
|
- name: role
|
||||||
|
in: body
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RoleRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Project member updated successfully.
|
||||||
|
'400':
|
||||||
|
description: Invalid role id, it should be 1,2 or 3, or invalid project id, or invalid member id.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: User in session does not have permission to the project.
|
||||||
|
'404':
|
||||||
|
description: project or project member does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
delete:
|
||||||
|
summary: Delete project member
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: project_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Relevant project ID.
|
||||||
|
- name: mid
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Member ID.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Project member deleted successfully.
|
||||||
|
'400':
|
||||||
|
description: The project id or project member id is invalid.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: User in session does not have permission to the project.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
/statistics:
|
/statistics:
|
||||||
get:
|
get:
|
||||||
summary: Get projects number and repositories number relevant to the user
|
summary: Get projects number and repositories number relevant to the user
|
||||||
@ -2307,8 +2468,153 @@ paths:
|
|||||||
$ref: '#/responses/UnsupportedMediaType'
|
$ref: '#/responses/UnsupportedMediaType'
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
/ldap/users/search:
|
/ldap/groups/search:
|
||||||
|
get:
|
||||||
|
summary: Search available ldap groups.
|
||||||
|
description: >
|
||||||
|
This endpoint searches the available ldap groups based on related
|
||||||
|
configuration parameters. Support searched by input ladp configuration,
|
||||||
|
load configuration from the system and specific filter.
|
||||||
|
parameters:
|
||||||
|
- name: groupname
|
||||||
|
in: query
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
description: Ldap group name
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Search ldap group successfully.
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/UserGroup'
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
/usergroups:
|
||||||
|
get:
|
||||||
|
summary: Get all user groups information
|
||||||
|
description: Get all user groups information
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Get user group successfully.
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/UserGroup'
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: User in session does not have permission to the user group.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
post:
|
post:
|
||||||
|
summary: Create user group
|
||||||
|
description: Create user group information
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: usergroup
|
||||||
|
in: body
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/UserGroup'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: User group created successfully.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: User in session does not have permission to the user group.
|
||||||
|
'404':
|
||||||
|
description: The LDAP group is not found.
|
||||||
|
'409':
|
||||||
|
description: An LDAP user group with same DN already exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
/usergroups/{group_id}:
|
||||||
|
get:
|
||||||
|
summary: Get user group information
|
||||||
|
description: Get user group information
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: group_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Group ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: User group get successfully.
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/UserGroup'
|
||||||
|
'400':
|
||||||
|
description: The user group id is invalid.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: User in session does not have permission to the user group.
|
||||||
|
'404':
|
||||||
|
description: User group does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
put:
|
||||||
|
summary: Update group information
|
||||||
|
description: Update user group information
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: group_id
|
||||||
|
in: path
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required: true
|
||||||
|
description: Group ID
|
||||||
|
- name: usergroup
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/UserGroup'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: User group updated successfully.
|
||||||
|
'400':
|
||||||
|
description: The user group id is invalid.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: Only admin has this authority.
|
||||||
|
'404':
|
||||||
|
description: User group does not exist.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
delete:
|
||||||
|
summary: Delete user group
|
||||||
|
description: Delete user group
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
parameters:
|
||||||
|
- name: group_id
|
||||||
|
type: integer
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: User group deleted successfully.
|
||||||
|
'400':
|
||||||
|
description: The user group id is invalid.
|
||||||
|
'401':
|
||||||
|
description: User need to log in first.
|
||||||
|
'403':
|
||||||
|
description: Only admin has this authority.
|
||||||
|
'500':
|
||||||
|
description: Unexpected internal errors.
|
||||||
|
/ldap/users/search:
|
||||||
|
get:
|
||||||
summary: Search available ldap users.
|
summary: Search available ldap users.
|
||||||
description: >
|
description: >
|
||||||
This endpoint searches the available ldap users based on related
|
This endpoint searches the available ldap users based on related
|
||||||
@ -2320,15 +2626,6 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
description: Registered user ID
|
description: Registered user ID
|
||||||
- name: ldap_conf
|
|
||||||
in: body
|
|
||||||
description: >-
|
|
||||||
ldap search configuration. ldapconf field can input ldap service
|
|
||||||
configuration. If this item are blank, will load default
|
|
||||||
configuration will load current configuration from the system.
|
|
||||||
required: false
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/LdapConf'
|
|
||||||
tags:
|
tags:
|
||||||
- Products
|
- Products
|
||||||
responses:
|
responses:
|
||||||
@ -2338,14 +2635,10 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/LdapUsers'
|
$ref: '#/definitions/LdapUsers'
|
||||||
'400':
|
|
||||||
description: Inviald ldap configuration parameters.
|
|
||||||
'401':
|
'401':
|
||||||
description: User need to login first.
|
description: User need to login first.
|
||||||
'403':
|
'403':
|
||||||
description: Only admin has this authority.
|
description: Only admin has this authority.
|
||||||
'415':
|
|
||||||
$ref: '#/responses/UnsupportedMediaType'
|
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
/ldap/users/import:
|
/ldap/users/import:
|
||||||
@ -2379,7 +2672,7 @@ paths:
|
|||||||
description: Only admin has this authority.
|
description: Only admin has this authority.
|
||||||
'415':
|
'415':
|
||||||
$ref: '#/responses/UnsupportedMediaType'
|
$ref: '#/responses/UnsupportedMediaType'
|
||||||
'500':
|
'404':
|
||||||
description: Failed import some users.
|
description: Failed import some users.
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
@ -3440,3 +3733,70 @@ definitions:
|
|||||||
update_time:
|
update_time:
|
||||||
type: string
|
type: string
|
||||||
description: The update time of label.
|
description: The update time of label.
|
||||||
|
ProjectMemberEntity:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
description: the project member id
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
description: the project id
|
||||||
|
entity_name:
|
||||||
|
type: string
|
||||||
|
description: the name of the group member.
|
||||||
|
role_name:
|
||||||
|
type: string
|
||||||
|
description: the name of the role
|
||||||
|
role_id:
|
||||||
|
type: integer
|
||||||
|
description: the role id
|
||||||
|
entity_id:
|
||||||
|
type: integer
|
||||||
|
description: the id of entity, if the member is an user, it is user_id in user table. if the member is an user group, it is the user group's ID in user_group table.
|
||||||
|
entity_type:
|
||||||
|
type: string
|
||||||
|
description: the entity's type, u for user entity, g for group entity.
|
||||||
|
ProjectMember:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
role_id:
|
||||||
|
type: integer
|
||||||
|
description: The role id 1 for projectAdmin, 2 for developer, 3 for guest
|
||||||
|
member_user:
|
||||||
|
$ref: '#/definitions/UserEntity'
|
||||||
|
member_group:
|
||||||
|
$ref: '#/definitions/UserGroup'
|
||||||
|
RoleRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
role_id:
|
||||||
|
type: integer
|
||||||
|
description: The role id 1 for projectAdmin, 2 for developer, 3 for guest
|
||||||
|
UserEntity:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: integer
|
||||||
|
description: The ID of the user.
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: The name of the user.
|
||||||
|
UserGroup:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
description: The ID of the user group
|
||||||
|
group_name:
|
||||||
|
type: string
|
||||||
|
description: The name of the user group
|
||||||
|
group_type:
|
||||||
|
type: integer
|
||||||
|
description: The group type, 1 for LDAP group.
|
||||||
|
ldap_group_dn:
|
||||||
|
type: string
|
||||||
|
description: The DN of the LDAP group if group type is 1 (LDAP group).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,4 +106,5 @@ const (
|
|||||||
DefaultJobserviceEndpoint = "http://jobservice:8080"
|
DefaultJobserviceEndpoint = "http://jobservice:8080"
|
||||||
DefaultUIEndpoint = "http://ui:8080"
|
DefaultUIEndpoint = "http://ui:8080"
|
||||||
DefaultNotaryEndpoint = "http://notary-server:4443"
|
DefaultNotaryEndpoint = "http://notary-server:4443"
|
||||||
|
LdapGroupType = 1
|
||||||
)
|
)
|
||||||
|
@ -50,6 +50,10 @@ func QueryUserGroup(query models.UserGroup) ([]*models.UserGroup, error) {
|
|||||||
sql += ` and ldap_group_dn = ? `
|
sql += ` and ldap_group_dn = ? `
|
||||||
sqlParam = append(sqlParam, query.LdapGroupDN)
|
sqlParam = append(sqlParam, query.LdapGroupDN)
|
||||||
}
|
}
|
||||||
|
if query.ID != 0 {
|
||||||
|
sql += ` and id = ? `
|
||||||
|
sqlParam = append(sqlParam, query.ID)
|
||||||
|
}
|
||||||
_, err := o.Raw(sql, sqlParam).QueryRows(&groups)
|
_, err := o.Raw(sql, sqlParam).QueryRows(&groups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -60,12 +64,14 @@ func QueryUserGroup(query models.UserGroup) ([]*models.UserGroup, error) {
|
|||||||
// GetUserGroup ...
|
// GetUserGroup ...
|
||||||
func GetUserGroup(id int) (*models.UserGroup, error) {
|
func GetUserGroup(id int) (*models.UserGroup, error) {
|
||||||
userGroup := models.UserGroup{ID: id}
|
userGroup := models.UserGroup{ID: id}
|
||||||
o := dao.GetOrmer()
|
userGroupList, err := QueryUserGroup(userGroup)
|
||||||
err := o.Read(&userGroup)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &userGroup, nil
|
if len(userGroupList) > 0 {
|
||||||
|
return userGroupList[0], nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUserGroup ...
|
// DeleteUserGroup ...
|
||||||
@ -92,3 +98,30 @@ func UpdateUserGroupName(id int, groupName string) error {
|
|||||||
_, err := o.Raw(sql, groupName, id).Exec()
|
_, err := o.Raw(sql, groupName, id).Exec()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnBoardUserGroup will check if a usergroup exists in usergroup table, if not insert the usergroup and
|
||||||
|
// put the id in the pointer of usergroup model, if it does exist, return the usergroup's profile.
|
||||||
|
// This is used for ldap and uaa authentication, such the usergroup can have an ID in Harbor.
|
||||||
|
// the keyAttribute and combinedKeyAttribute are key columns used to check duplicate usergroup in harbor
|
||||||
|
func OnBoardUserGroup(g *models.UserGroup, keyAttribute string, combinedKeyAttributes ...string) error {
|
||||||
|
o := dao.GetOrmer()
|
||||||
|
created, ID, err := o.ReadOrCreate(g, keyAttribute, combinedKeyAttributes...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
g.ID = int(ID)
|
||||||
|
} else {
|
||||||
|
prevGroup, err := GetUserGroup(int(ID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.ID = prevGroup.ID
|
||||||
|
g.GroupName = prevGroup.GroupName
|
||||||
|
g.GroupType = prevGroup.GroupType
|
||||||
|
g.LdapGroupDN = prevGroup.LdapGroupDN
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
@ -80,7 +81,7 @@ func TestAddUserGroup(t *testing.T) {
|
|||||||
want int
|
want int
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"Insert an ldap user group", args{userGroup: models.UserGroup{GroupName: "sample_group", GroupType: 1, LdapGroupDN: "sample_ldap_dn_string"}}, 0, false},
|
{"Insert an ldap user group", args{userGroup: models.UserGroup{GroupName: "sample_group", GroupType: common.LdapGroupType, LdapGroupDN: "sample_ldap_dn_string"}}, 0, false},
|
||||||
{"Insert other user group", args{userGroup: models.UserGroup{GroupName: "other_group", GroupType: 3, LdapGroupDN: "other information"}}, 0, false},
|
{"Insert other user group", args{userGroup: models.UserGroup{GroupName: "other_group", GroupType: 3, LdapGroupDN: "other information"}}, 0, false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -108,8 +109,8 @@ func TestQueryUserGroup(t *testing.T) {
|
|||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"Query all user group", args{query: models.UserGroup{GroupName: "test_group_01"}}, 1, false},
|
{"Query all user group", args{query: models.UserGroup{GroupName: "test_group_01"}}, 1, false},
|
||||||
{"Query all ldap group", args{query: models.UserGroup{GroupType: 1}}, 2, false},
|
{"Query all ldap group", args{query: models.UserGroup{GroupType: common.LdapGroupType}}, 2, false},
|
||||||
{"Query ldap group with group property", args{query: models.UserGroup{GroupType: 1, LdapGroupDN: "CN=harbor_users,OU=sample,OU=vmware,DC=harbor,DC=com"}}, 1, false},
|
{"Query ldap group with group property", args{query: models.UserGroup{GroupType: common.LdapGroupType, LdapGroupDN: "CN=harbor_users,OU=sample,OU=vmware,DC=harbor,DC=com"}}, 1, false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -126,7 +127,7 @@ func TestQueryUserGroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserGroup(t *testing.T) {
|
func TestGetUserGroup(t *testing.T) {
|
||||||
userGroup := models.UserGroup{GroupName: "insert_group", GroupType: 1, LdapGroupDN: "ldap_dn_string"}
|
userGroup := models.UserGroup{GroupName: "insert_group", GroupType: common.LdapGroupType, LdapGroupDN: "ldap_dn_string"}
|
||||||
result, err := AddUserGroup(userGroup)
|
result, err := AddUserGroup(userGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error occurred when AddUserGroup: %v", err)
|
t.Errorf("Error occurred when AddUserGroup: %v", err)
|
||||||
@ -142,6 +143,7 @@ func TestGetUserGroup(t *testing.T) {
|
|||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"Get User Group", args{id: result}, "insert_group", false},
|
{"Get User Group", args{id: result}, "insert_group", false},
|
||||||
|
{"Get User Group does not exist", args{id: 9999}, "insert_group", false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -150,7 +152,7 @@ func TestGetUserGroup(t *testing.T) {
|
|||||||
t.Errorf("GetUserGroup() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("GetUserGroup() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if got.GroupName != tt.want {
|
if got != nil && got.GroupName != tt.want {
|
||||||
t.Errorf("GetUserGroup() = %v, want %v", got.GroupName, tt.want)
|
t.Errorf("GetUserGroup() = %v, want %v", got.GroupName, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -216,3 +218,34 @@ func TestDeleteUserGroup(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOnBoardUserGroup(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
g *models.UserGroup
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"OnBoardUserGroup",
|
||||||
|
args{g: &models.UserGroup{
|
||||||
|
GroupName: "harbor_example",
|
||||||
|
LdapGroupDN: "cn=harbor_example,ou=groups,dc=example,dc=com",
|
||||||
|
GroupType: common.LdapGroupType}},
|
||||||
|
false},
|
||||||
|
{"OnBoardUserGroup second time",
|
||||||
|
args{g: &models.UserGroup{
|
||||||
|
GroupName: "harbor_example",
|
||||||
|
LdapGroupDN: "cn=harbor_example,ou=groups,dc=example,dc=com",
|
||||||
|
GroupType: common.LdapGroupType}},
|
||||||
|
false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := OnBoardUserGroup(tt.args.g, "LdapGroupDN", "GroupType"); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("OnBoardUserGroup() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -74,6 +74,7 @@ func GetProjectMember(queryMember models.Member) ([]*models.Member, error) {
|
|||||||
func AddProjectMember(member models.Member) (int, error) {
|
func AddProjectMember(member models.Member) (int, error) {
|
||||||
|
|
||||||
log.Debugf("Adding project member %+v", member)
|
log.Debugf("Adding project member %+v", member)
|
||||||
|
|
||||||
o := dao.GetOrmer()
|
o := dao.GetOrmer()
|
||||||
|
|
||||||
if member.EntityID <= 0 {
|
if member.EntityID <= 0 {
|
||||||
@ -98,9 +99,7 @@ func AddProjectMember(member models.Member) (int, error) {
|
|||||||
|
|
||||||
// UpdateProjectMemberRole updates the record in table project_member, only role can be changed
|
// UpdateProjectMemberRole updates the record in table project_member, only role can be changed
|
||||||
func UpdateProjectMemberRole(pmID int, role int) error {
|
func UpdateProjectMemberRole(pmID int, role int) error {
|
||||||
if role <= 0 || role >= 3 {
|
|
||||||
return fmt.Errorf("Failed to update project member, role is not in 0,1,2, role:%v", role)
|
|
||||||
}
|
|
||||||
o := dao.GetOrmer()
|
o := dao.GetOrmer()
|
||||||
sql := "update project_member set role = ? where id = ? "
|
sql := "update project_member set role = ? where id = ? "
|
||||||
_, err := o.Raw(sql, role, pmID).Exec()
|
_, err := o.Raw(sql, role, pmID).Exec()
|
||||||
|
@ -58,5 +58,5 @@ type LdapFailedImportUser struct {
|
|||||||
// LdapGroup ...
|
// LdapGroup ...
|
||||||
type LdapGroup struct {
|
type LdapGroup struct {
|
||||||
GroupName string `json:"group_name,omitempty"`
|
GroupName string `json:"group_name,omitempty"`
|
||||||
GroupDN string `json:"group_dn,omitempty"`
|
GroupDN string `json:"ldap_group_dn,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -108,9 +108,11 @@ func init() {
|
|||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/?:name", &MetadataAPI{}, "get:Get")
|
beego.Router("/api/projects/:id([0-9]+)/metadatas/?:name", &MetadataAPI{}, "get:Get")
|
||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/", &MetadataAPI{}, "post:Post")
|
beego.Router("/api/projects/:id([0-9]+)/metadatas/", &MetadataAPI{}, "post:Post")
|
||||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/:name", &MetadataAPI{}, "put:Put;delete:Delete")
|
beego.Router("/api/projects/:id([0-9]+)/metadatas/:name", &MetadataAPI{}, "put:Put;delete:Delete")
|
||||||
|
beego.Router("/api/projects/:pid([0-9]+)/projectmembers/?:pmid([0-9]+)", &ProjectMemberAPI{})
|
||||||
beego.Router("/api/repositories", &RepositoryAPI{})
|
beego.Router("/api/repositories", &RepositoryAPI{})
|
||||||
beego.Router("/api/statistics", &StatisticAPI{})
|
beego.Router("/api/statistics", &StatisticAPI{})
|
||||||
beego.Router("/api/users/?:id", &UserAPI{})
|
beego.Router("/api/users/?:id", &UserAPI{})
|
||||||
|
beego.Router("/api/usergroups/?:ugid([0-9]+)", &UserGroupAPI{})
|
||||||
beego.Router("/api/logs", &LogAPI{})
|
beego.Router("/api/logs", &LogAPI{})
|
||||||
beego.Router("/api/repositories/*", &RepositoryAPI{}, "put:Put")
|
beego.Router("/api/repositories/*", &RepositoryAPI{}, "put:Put")
|
||||||
beego.Router("/api/repositories/*/labels", &RepositoryLabelAPI{}, "get:GetOfRepository;post:AddToRepository")
|
beego.Router("/api/repositories/*/labels", &RepositoryLabelAPI{}, "get:GetOfRepository;post:AddToRepository")
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"fmt"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
||||||
@ -62,8 +62,7 @@ func (l *LdapAPI) Ping() {
|
|||||||
if string(l.Ctx.Input.RequestBody) == "" {
|
if string(l.Ctx.Input.RequestBody) == "" {
|
||||||
ldapSession, err = ldapUtils.LoadSystemLdapConfig()
|
ldapSession, err = ldapUtils.LoadSystemLdapConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Can't load system configuration, error: %v", err)
|
l.HandleInternalServerError(fmt.Sprintf("Can't load system configuration, error: %v", err))
|
||||||
l.RenderError(http.StatusInternalServerError, pingErrorMessage)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ldapSession.ConnectionTest()
|
err = ldapSession.ConnectionTest()
|
||||||
@ -73,9 +72,7 @@ func (l *LdapAPI) Ping() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("ldap connect fail, error: %v", err)
|
l.HandleInternalServerError(fmt.Sprintf("LDAP connect fail, error: %v", err))
|
||||||
// do not return any detail information of the error, or may cause SSRF security issue #3755
|
|
||||||
l.RenderError(http.StatusBadRequest, pingErrorMessage)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,24 +81,9 @@ func (l *LdapAPI) Ping() {
|
|||||||
func (l *LdapAPI) Search() {
|
func (l *LdapAPI) Search() {
|
||||||
var err error
|
var err error
|
||||||
var ldapUsers []models.LdapUser
|
var ldapUsers []models.LdapUser
|
||||||
var ldapConfs models.LdapConf
|
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
||||||
var ldapSession *ldapUtils.Session
|
|
||||||
l.Ctx.Input.CopyBody(1 << 32)
|
|
||||||
if string(l.Ctx.Input.RequestBody) == "" {
|
|
||||||
ldapSession, err = ldapUtils.LoadSystemLdapConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("can't load system configuration, error: %v", err)
|
|
||||||
l.RenderError(http.StatusInternalServerError, loadSystemErrorMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
l.DecodeJSONReqAndValidate(&ldapConfs)
|
|
||||||
ldapSession, err = ldapUtils.CreateWithConfig(ldapConfs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ldapSession.Open(); err != nil {
|
if err = ldapSession.Open(); err != nil {
|
||||||
log.Errorf("can't Open ldap session, error: %v", err)
|
l.HandleInternalServerError(fmt.Sprintf("Can't Open LDAP session, error: %v", err))
|
||||||
l.RenderError(http.StatusInternalServerError, canNotOpenLdapSession)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer ldapSession.Close()
|
defer ldapSession.Close()
|
||||||
@ -111,8 +93,7 @@ func (l *LdapAPI) Search() {
|
|||||||
ldapUsers, err = ldapSession.SearchUser(searchName)
|
ldapUsers, err = ldapSession.SearchUser(searchName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Ldap search fail, error: %v", err)
|
l.HandleInternalServerError(fmt.Sprintf("LDAP search fail, error: %v", err))
|
||||||
l.RenderError(http.StatusBadRequest, searchLdapFailMessage)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,14 +113,12 @@ func (l *LdapAPI) ImportUser() {
|
|||||||
ldapFailedImportUsers, err := importUsers(ldapConfs, ldapImportUsers.LdapUIDList)
|
ldapFailedImportUsers, err := importUsers(ldapConfs, ldapImportUsers.LdapUIDList)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Ldap import user fail, error: %v", err)
|
l.HandleInternalServerError(fmt.Sprintf("LDAP import user fail, error: %v", err))
|
||||||
l.RenderError(http.StatusBadRequest, importUserError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ldapFailedImportUsers) > 0 {
|
if len(ldapFailedImportUsers) > 0 {
|
||||||
log.Errorf("Import ldap user have internal error")
|
l.HandleNotFound("Import LDAP user have internal error")
|
||||||
l.RenderError(http.StatusInternalServerError, importUserError)
|
|
||||||
l.Data["json"] = ldapFailedImportUsers
|
l.Data["json"] = ldapFailedImportUsers
|
||||||
l.ServeJSON()
|
l.ServeJSON()
|
||||||
return
|
return
|
||||||
@ -153,12 +132,12 @@ func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.
|
|||||||
|
|
||||||
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("can't load system configuration, error: %v", err)
|
log.Errorf("Can't load system configuration, error: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ldapSession.Open(); err != nil {
|
if err = ldapSession.Open(); err != nil {
|
||||||
log.Errorf("Can't connect to ldap, error: %v", err)
|
log.Errorf("Can't connect to LDAP, error: %v", err)
|
||||||
}
|
}
|
||||||
defer ldapSession.Close()
|
defer ldapSession.Close()
|
||||||
|
|
||||||
@ -182,7 +161,7 @@ func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.
|
|||||||
u.UID = tempUID
|
u.UID = tempUID
|
||||||
u.Error = "failed_search_user"
|
u.Error = "failed_search_user"
|
||||||
failedImportUser = append(failedImportUser, u)
|
failedImportUser = append(failedImportUser, u)
|
||||||
log.Errorf("Invalid ldap search request for %s, error: %v", tempUID, err)
|
log.Errorf("Invalid LDAP search request for %s, error: %v", tempUID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,3 +190,22 @@ func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.
|
|||||||
|
|
||||||
return failedImportUser, nil
|
return failedImportUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchGroup ... Search LDAP by groupname
|
||||||
|
func (l *LdapAPI) SearchGroup() {
|
||||||
|
searchName := l.GetString("groupname")
|
||||||
|
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("Can't get LDAP system config, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ldapSession.Open()
|
||||||
|
defer ldapSession.Close()
|
||||||
|
ldapGroups, err := ldapSession.SearchGroupByName(searchName)
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("Can't search LDAP group by name, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.Data["json"] = ldapGroups
|
||||||
|
l.ServeJSON()
|
||||||
|
}
|
||||||
|
206
src/ui/api/projectmember.go
Normal file
206
src/ui/api/projectmember.go
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/dao/project"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
|
||||||
|
type ProjectMemberAPI struct {
|
||||||
|
BaseController
|
||||||
|
id int
|
||||||
|
entityID int
|
||||||
|
entityType string
|
||||||
|
project *models.Project
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare validates the URL and parms
|
||||||
|
func (pma *ProjectMemberAPI) Prepare() {
|
||||||
|
pma.BaseController.Prepare()
|
||||||
|
|
||||||
|
if !pma.SecurityCtx.IsAuthenticated() {
|
||||||
|
pma.HandleUnauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pid, err := pma.GetInt64FromPath(":pid")
|
||||||
|
if err != nil || pid <= 0 {
|
||||||
|
text := "invalid project ID: "
|
||||||
|
if err != nil {
|
||||||
|
text += err.Error()
|
||||||
|
} else {
|
||||||
|
text += fmt.Sprintf("%d", pid)
|
||||||
|
}
|
||||||
|
pma.HandleBadRequest(text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
project, err := pma.ProjectMgr.Get(pid)
|
||||||
|
if err != nil {
|
||||||
|
pma.ParseAndHandleError(fmt.Sprintf("failed to get project %d", pid), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if project == nil {
|
||||||
|
pma.HandleNotFound(fmt.Sprintf("project %d not found", pid))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pma.project = project
|
||||||
|
|
||||||
|
if !(pma.Ctx.Input.IsGet() && pma.SecurityCtx.HasReadPerm(pid) ||
|
||||||
|
pma.SecurityCtx.HasAllPerm(pid)) {
|
||||||
|
pma.HandleForbidden(pma.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pmid, err := pma.GetInt64FromPath(":pmid")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get pmid from path, error %v", err)
|
||||||
|
}
|
||||||
|
if pmid <= 0 && (pma.Ctx.Input.IsPut() || pma.Ctx.Input.IsDelete()) {
|
||||||
|
pma.HandleBadRequest(fmt.Sprintf("The project member id is invalid, pmid:%s", pma.GetStringFromPath(":pmid")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pma.id = int(pmid)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get ...
|
||||||
|
func (pma *ProjectMemberAPI) Get() {
|
||||||
|
projectID := pma.project.ProjectID
|
||||||
|
queryMember := models.Member{}
|
||||||
|
queryMember.ProjectID = projectID
|
||||||
|
pma.Data["json"] = make([]models.Member, 0)
|
||||||
|
if pma.id == 0 {
|
||||||
|
//member id not set, return all member of current project
|
||||||
|
memberList, err := project.GetProjectMember(queryMember)
|
||||||
|
if err != nil {
|
||||||
|
pma.HandleInternalServerError(fmt.Sprintf("Failed to query database for member list, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(memberList) > 0 {
|
||||||
|
pma.Data["json"] = memberList
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//return a specific member
|
||||||
|
queryMember.ID = pma.id
|
||||||
|
memberList, err := project.GetProjectMember(queryMember)
|
||||||
|
if err != nil {
|
||||||
|
pma.HandleInternalServerError(fmt.Sprintf("Failed to query database for member list, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(memberList) == 0 {
|
||||||
|
pma.HandleNotFound(fmt.Sprintf("The project member does not exit, pmid:%v", pma.id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pma.Data["json"] = memberList[0]
|
||||||
|
}
|
||||||
|
pma.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post ... Add a project member
|
||||||
|
func (pma *ProjectMemberAPI) Post() {
|
||||||
|
projectID := pma.project.ProjectID
|
||||||
|
var request models.MemberReq
|
||||||
|
pma.DecodeJSONReq(&request)
|
||||||
|
pmid, err := AddOrUpdateProjectMember(projectID, request)
|
||||||
|
if err != nil {
|
||||||
|
pma.HandleInternalServerError(fmt.Sprintf("Failed to add project member, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pma.Redirect(http.StatusCreated, strconv.FormatInt(int64(pmid), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put ... Update an exist project member
|
||||||
|
func (pma *ProjectMemberAPI) Put() {
|
||||||
|
pid := pma.project.ProjectID
|
||||||
|
pmID := pma.id
|
||||||
|
var req models.Member
|
||||||
|
pma.DecodeJSONReq(&req)
|
||||||
|
if req.Role < 1 || req.Role > 3 {
|
||||||
|
pma.HandleBadRequest(fmt.Sprintf("Invalid role id %v", req.Role))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := project.UpdateProjectMemberRole(pmID, req.Role)
|
||||||
|
if err != nil {
|
||||||
|
pma.HandleInternalServerError(fmt.Sprintf("Failed to update DB to add project user role, project id: %d, pmid : %d, role id: %d", pid, pmID, req.Role))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete ...
|
||||||
|
func (pma *ProjectMemberAPI) Delete() {
|
||||||
|
pmid := pma.id
|
||||||
|
err := project.DeleteProjectMemberByID(pmid)
|
||||||
|
if err != nil {
|
||||||
|
pma.HandleInternalServerError(fmt.Sprintf("Failed to delete project roles for user, project member id: %d, error: %v", pmid, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOrUpdateProjectMember ... If the project member relationship does not exist, create it. if exist, update it
|
||||||
|
func AddOrUpdateProjectMember(projectID int64, request models.MemberReq) (int, error) {
|
||||||
|
var member models.Member
|
||||||
|
member.ProjectID = projectID
|
||||||
|
member.Role = request.Role
|
||||||
|
if request.MemberUser.UserID > 0 {
|
||||||
|
member.EntityID = request.MemberUser.UserID
|
||||||
|
member.EntityType = common.UserMember
|
||||||
|
} else if request.MemberGroup.ID > 0 {
|
||||||
|
member.EntityID = request.MemberGroup.ID
|
||||||
|
member.EntityType = common.GroupMember
|
||||||
|
} else if len(request.MemberUser.Username) > 0 {
|
||||||
|
member.EntityType = common.UserMember
|
||||||
|
userID, err := auth.SearchAndOnBoardUser(request.MemberUser.Username)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
member.EntityID = userID
|
||||||
|
} else if len(request.MemberGroup.LdapGroupDN) > 0 {
|
||||||
|
member.EntityType = common.GroupMember
|
||||||
|
//If groupname provided, use the provided groupname
|
||||||
|
//If ldap group already exist in harbor, use the previous group name
|
||||||
|
groupID, err := auth.SearchAndOnBoardGroup(request.MemberGroup.LdapGroupDN, request.MemberGroup.GroupName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
member.EntityID = groupID
|
||||||
|
}
|
||||||
|
if member.EntityID <= 0 {
|
||||||
|
return 0, fmt.Errorf("Can not get valid member entity, request: %+v", request)
|
||||||
|
}
|
||||||
|
memberList, err := project.GetProjectMember(models.Member{
|
||||||
|
ProjectID: member.ProjectID,
|
||||||
|
EntityID: member.EntityID,
|
||||||
|
EntityType: member.EntityType,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(memberList) > 0 {
|
||||||
|
project.UpdateProjectMemberRole(memberList[0].ID, member.Role)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if member.Role < 1 || member.Role > 3 {
|
||||||
|
return 0, fmt.Errorf("Failed to update project member, role is not in 1,2,3 role:%v", member.Role)
|
||||||
|
}
|
||||||
|
return project.AddProjectMember(member)
|
||||||
|
}
|
210
src/ui/api/projectmember_test.go
Normal file
210
src/ui/api/projectmember_test.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/dao/project"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProjectMemberAPI_Get(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/projects/1/projectmembers",
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
//200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/projects/1/projectmembers",
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
//400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/projects/0/projectmembers",
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
// 404
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/projects/1/projectmembers/121",
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProjectMemberAPI_Post(t *testing.T) {
|
||||||
|
userID, err := dao.Register(models.User{
|
||||||
|
Username: "restuser",
|
||||||
|
Password: "Harbor12345",
|
||||||
|
Email: "restuser@example.com",
|
||||||
|
})
|
||||||
|
defer dao.DeleteUser(int(userID))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when create user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/projects/1/projectmembers",
|
||||||
|
bodyJSON: &models.MemberReq{
|
||||||
|
Role: 1,
|
||||||
|
MemberUser: models.User{
|
||||||
|
UserID: int(userID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/projects/1/projectmembers",
|
||||||
|
bodyJSON: &models.MemberReq{
|
||||||
|
Role: 1,
|
||||||
|
MemberUser: models.User{
|
||||||
|
UserID: int(userID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusCreated,
|
||||||
|
},
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/projects/1/projectmembers",
|
||||||
|
bodyJSON: &models.MemberReq{
|
||||||
|
Role: 1,
|
||||||
|
MemberUser: models.User{
|
||||||
|
UserID: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProjectMemberAPI_PutAndDelete(t *testing.T) {
|
||||||
|
|
||||||
|
userID, err := dao.Register(models.User{
|
||||||
|
Username: "restuser",
|
||||||
|
Password: "Harbor12345",
|
||||||
|
Email: "restuser@example.com",
|
||||||
|
})
|
||||||
|
defer dao.DeleteUser(int(userID))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when create user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ID, err := project.AddProjectMember(models.Member{
|
||||||
|
ProjectID: 1,
|
||||||
|
Role: 1,
|
||||||
|
EntityID: int(userID),
|
||||||
|
EntityType: "u",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when add project member: %v", err)
|
||||||
|
}
|
||||||
|
URL := fmt.Sprintf("/api/projects/1/projectmembers/%v", ID)
|
||||||
|
badURL := fmt.Sprintf("/api/projects/1/projectmembers/%v", 0)
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: URL,
|
||||||
|
bodyJSON: &models.Member{
|
||||||
|
Role: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: URL,
|
||||||
|
bodyJSON: &models.Member{
|
||||||
|
Role: 2,
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
// 400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: badURL,
|
||||||
|
bodyJSON: &models.Member{
|
||||||
|
Role: 2,
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
// 400
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: URL,
|
||||||
|
bodyJSON: &models.Member{
|
||||||
|
Role: -2,
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: URL,
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
|
||||||
|
}
|
146
src/ui/api/usergroup.go
Normal file
146
src/ui/api/usergroup.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
"github.com/vmware/harbor/src/common/dao/group"
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserGroupAPI ...
|
||||||
|
type UserGroupAPI struct {
|
||||||
|
BaseController
|
||||||
|
id int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare validates the URL and parms
|
||||||
|
func (uga *UserGroupAPI) Prepare() {
|
||||||
|
uga.BaseController.Prepare()
|
||||||
|
if !uga.SecurityCtx.IsAuthenticated() {
|
||||||
|
uga.HandleUnauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ugid, err := uga.GetInt64FromPath(":ugid")
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("failed to parse user group id, error: %v", err)
|
||||||
|
}
|
||||||
|
if ugid <= 0 && (uga.Ctx.Input.IsPut() || uga.Ctx.Input.IsDelete()) {
|
||||||
|
uga.HandleBadRequest(fmt.Sprintf("invalid user group ID: %s", uga.GetStringFromPath(":ugid")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uga.id = int(ugid)
|
||||||
|
//Common user can create/update, only harbor admin can delete user group.
|
||||||
|
if uga.Ctx.Input.IsDelete() && !uga.SecurityCtx.IsSysAdmin() {
|
||||||
|
uga.HandleForbidden(uga.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ...
|
||||||
|
func (uga *UserGroupAPI) Get() {
|
||||||
|
ID := uga.id
|
||||||
|
uga.Data["json"] = make([]models.UserGroup, 0)
|
||||||
|
if ID == 0 {
|
||||||
|
//user group id not set, return all user group
|
||||||
|
query := models.UserGroup{GroupType: common.LdapGroupType} //Current query LDAP group only
|
||||||
|
userGroupList, err := group.QueryUserGroup(query)
|
||||||
|
if err != nil {
|
||||||
|
uga.HandleInternalServerError(fmt.Sprintf("Failed to query database for user group list, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(userGroupList) > 0 {
|
||||||
|
uga.Data["json"] = userGroupList
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//return a specific user group
|
||||||
|
userGroup, err := group.GetUserGroup(ID)
|
||||||
|
if userGroup == nil {
|
||||||
|
uga.HandleNotFound("The user group does not exist.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
uga.HandleInternalServerError(fmt.Sprintf("Failed to query database for user group list, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uga.Data["json"] = userGroup
|
||||||
|
}
|
||||||
|
uga.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post ... Create User Group
|
||||||
|
func (uga *UserGroupAPI) Post() {
|
||||||
|
userGroup := models.UserGroup{}
|
||||||
|
uga.DecodeJSONReq(&userGroup)
|
||||||
|
userGroup.ID = 0
|
||||||
|
userGroup.GroupType = common.LdapGroupType
|
||||||
|
query := models.UserGroup{GroupType: userGroup.GroupType, LdapGroupDN: userGroup.LdapGroupDN}
|
||||||
|
result, err := group.QueryUserGroup(query)
|
||||||
|
if err != nil {
|
||||||
|
uga.HandleInternalServerError(fmt.Sprintf("Error occurred in add user group, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result) > 0 {
|
||||||
|
uga.HandleConflict("Error occurred in add user group, duplicate user group exist.")
|
||||||
|
}
|
||||||
|
// User can not add ldap group when the ldap server is offline
|
||||||
|
ldapGroup, err := auth.SearchGroup(userGroup.LdapGroupDN)
|
||||||
|
if err != nil {
|
||||||
|
uga.HandleInternalServerError(fmt.Sprintf("Error occurred in search user group. error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ldapGroup == nil {
|
||||||
|
uga.HandleNotFound("The LDAP group is not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupID, err := group.AddUserGroup(userGroup)
|
||||||
|
if err != nil {
|
||||||
|
uga.HandleInternalServerError(fmt.Sprintf("Error occurred in add user group, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uga.Redirect(http.StatusCreated, strconv.FormatInt(int64(groupID), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put ... Only support update name
|
||||||
|
func (uga *UserGroupAPI) Put() {
|
||||||
|
userGroup := models.UserGroup{}
|
||||||
|
uga.DecodeJSONReq(&userGroup)
|
||||||
|
ID := uga.id
|
||||||
|
userGroup.GroupType = common.LdapGroupType
|
||||||
|
log.Debugf("Updated user group %v", userGroup)
|
||||||
|
err := group.UpdateUserGroupName(ID, userGroup.GroupName)
|
||||||
|
if err != nil {
|
||||||
|
uga.HandleInternalServerError(fmt.Sprintf("Error occurred in update user group, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete ...
|
||||||
|
func (uga *UserGroupAPI) Delete() {
|
||||||
|
err := group.DeleteUserGroup(uga.id)
|
||||||
|
if err != nil {
|
||||||
|
uga.HandleInternalServerError(fmt.Sprintf("Error occurred in update user group, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
154
src/ui/api/usergroup_test.go
Normal file
154
src/ui/api/usergroup_test.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/dao/group"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
URL = "/api/usergroups"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserGroupAPI_GetAndDelete(t *testing.T) {
|
||||||
|
|
||||||
|
groupID, err := group.AddUserGroup(models.UserGroup{
|
||||||
|
GroupName: "harbor_users",
|
||||||
|
LdapGroupDN: "cn=harbor_users,ou=groups,dc=example,dc=com",
|
||||||
|
GroupType: common.LdapGroupType,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when AddUserGroup: %v", err)
|
||||||
|
}
|
||||||
|
defer group.DeleteUserGroup(groupID)
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
// 401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: URL,
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: fmt.Sprintf("/api/usergroups/%d", groupID),
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: fmt.Sprintf("/api/usergroups"),
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
// 200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodDelete,
|
||||||
|
url: fmt.Sprintf("/api/usergroups/%d", groupID),
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserGroupAPI_Post(t *testing.T) {
|
||||||
|
groupID, err := group.AddUserGroup(models.UserGroup{
|
||||||
|
GroupName: "harbor_group",
|
||||||
|
LdapGroupDN: "cn=harbor_group,ou=groups,dc=example,dc=com",
|
||||||
|
GroupType: common.LdapGroupType,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when AddUserGroup: %v", err)
|
||||||
|
}
|
||||||
|
defer group.DeleteUserGroup(groupID)
|
||||||
|
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
//409
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/usergroups",
|
||||||
|
bodyJSON: &models.UserGroup{
|
||||||
|
GroupName: "harbor_group",
|
||||||
|
LdapGroupDN: "cn=harbor_group,ou=groups,dc=example,dc=com",
|
||||||
|
GroupType: common.LdapGroupType,
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusConflict,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserGroupAPI_Put(t *testing.T) {
|
||||||
|
groupID, err := group.AddUserGroup(models.UserGroup{
|
||||||
|
GroupName: "harbor_group",
|
||||||
|
LdapGroupDN: "cn=harbor_groups,ou=groups,dc=example,dc=com",
|
||||||
|
GroupType: common.LdapGroupType,
|
||||||
|
})
|
||||||
|
defer group.DeleteUserGroup(groupID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when AddUserGroup: %v", err)
|
||||||
|
}
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
//401
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("/api/usergroups/%d", groupID),
|
||||||
|
bodyJSON: &models.UserGroup{
|
||||||
|
GroupName: "my_group",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
//200
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPut,
|
||||||
|
url: fmt.Sprintf("/api/usergroups/%d", groupID),
|
||||||
|
bodyJSON: &models.UserGroup{
|
||||||
|
GroupName: "my_group",
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
@ -68,7 +68,7 @@ func TestDefaultAuthenticate(t *testing.T) {
|
|||||||
authHelper := DefaultAuthenticateHelper{}
|
authHelper := DefaultAuthenticateHelper{}
|
||||||
m := models.AuthModel{}
|
m := models.AuthModel{}
|
||||||
user, err := authHelper.Authenticate(m)
|
user, err := authHelper.Authenticate(m)
|
||||||
if user != nil || err != nil {
|
if user != nil || err == nil {
|
||||||
t.Fatal("Default implementation should return nil")
|
t.Fatal("Default implementation should return nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,8 +77,26 @@ func TestDefaultOnBoardUser(t *testing.T) {
|
|||||||
user := &models.User{}
|
user := &models.User{}
|
||||||
authHelper := DefaultAuthenticateHelper{}
|
authHelper := DefaultAuthenticateHelper{}
|
||||||
err := authHelper.OnBoardUser(user)
|
err := authHelper.OnBoardUser(user)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
t.Fatal("Default implementation should return nil")
|
t.Fatal("Default implementation should return error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultMethods(t *testing.T) {
|
||||||
|
authHelper := DefaultAuthenticateHelper{}
|
||||||
|
_, err := authHelper.SearchUser("sample")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Default implementation should return error")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = authHelper.SearchGroup("sample")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Default implementation should return error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = authHelper.OnBoardGroup(&models.UserGroup{}, "sample")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Default implementation should return error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -55,9 +56,13 @@ type AuthenticateHelper interface {
|
|||||||
// put the id in the pointer of user model, if it does exist, fill in the user model based
|
// put the id in the pointer of user model, if it does exist, fill in the user model based
|
||||||
// on the data record of the user
|
// on the data record of the user
|
||||||
OnBoardUser(u *models.User) error
|
OnBoardUser(u *models.User) error
|
||||||
|
// Create a group in harbor DB, if altGroupName is not empty, take the altGroupName as groupName in harbor DB.
|
||||||
|
OnBoardGroup(g *models.UserGroup, altGroupName string) error
|
||||||
// Get user information from account repository
|
// Get user information from account repository
|
||||||
SearchUser(username string) (*models.User, error)
|
SearchUser(username string) (*models.User, error)
|
||||||
// Update user information after authenticate, such as Onboard or sync info etc
|
// Search a group based on specific authentication
|
||||||
|
SearchGroup(groupDN string) (*models.UserGroup, error)
|
||||||
|
// Update user information after authenticate, such as OnBoard or sync info etc
|
||||||
PostAuthenticate(u *models.User) error
|
PostAuthenticate(u *models.User) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,26 +72,36 @@ type DefaultAuthenticateHelper struct {
|
|||||||
|
|
||||||
// Authenticate ...
|
// Authenticate ...
|
||||||
func (d *DefaultAuthenticateHelper) Authenticate(m models.AuthModel) (*models.User, error) {
|
func (d *DefaultAuthenticateHelper) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||||
return nil, nil
|
return nil, errors.New("Not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
||||||
// put the id in the pointer of user model, if it does exist, fill in the user model based
|
// put the id in the pointer of user model, if it does exist, fill in the user model based
|
||||||
// on the data record of the user
|
// on the data record of the user
|
||||||
func (d *DefaultAuthenticateHelper) OnBoardUser(u *models.User) error {
|
func (d *DefaultAuthenticateHelper) OnBoardUser(u *models.User) error {
|
||||||
return nil
|
return errors.New("Not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
//SearchUser - Get user information from account repository
|
//SearchUser - Get user information from account repository
|
||||||
func (d *DefaultAuthenticateHelper) SearchUser(username string) (*models.User, error) {
|
func (d *DefaultAuthenticateHelper) SearchUser(username string) (*models.User, error) {
|
||||||
return nil, nil
|
return nil, errors.New("Not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
//PostAuthenticate - Update user information after authenticate, such as Onboard or sync info etc
|
//PostAuthenticate - Update user information after authenticate, such as OnBoard or sync info etc
|
||||||
func (d *DefaultAuthenticateHelper) PostAuthenticate(u *models.User) error {
|
func (d *DefaultAuthenticateHelper) PostAuthenticate(u *models.User) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnBoardGroup - OnBoardGroup, it will set the ID of the user group, if altGroupName is not empty, take the altGroupName as groupName in harbor DB.
|
||||||
|
func (d *DefaultAuthenticateHelper) OnBoardGroup(u *models.UserGroup, altGroupName string) error {
|
||||||
|
return errors.New("Not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchGroup - Search ldap group by group key, groupKey is the unique attribute of group in authenticator, for LDAP, the key is group DN
|
||||||
|
func (d *DefaultAuthenticateHelper) SearchGroup(groupKey string) (*models.UserGroup, error) {
|
||||||
|
return nil, errors.New("Not supported")
|
||||||
|
}
|
||||||
|
|
||||||
var registry = make(map[string]AuthenticateHelper)
|
var registry = make(map[string]AuthenticateHelper)
|
||||||
|
|
||||||
// Register add different authenticators to registry map.
|
// Register add different authenticators to registry map.
|
||||||
@ -128,9 +143,7 @@ func Login(m models.AuthModel) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = authenticator.PostAuthenticate(user)
|
err = authenticator.PostAuthenticate(user)
|
||||||
|
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +179,51 @@ func SearchUser(username string) (*models.User, error) {
|
|||||||
return helper.SearchUser(username)
|
return helper.SearchUser(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnBoardGroup - Create a user group in harbor db, if altGroupName is not empty, take the altGroupName as groupName in harbor DB
|
||||||
|
func OnBoardGroup(userGroup *models.UserGroup, altGroupName string) error {
|
||||||
|
helper, err := getHelper()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return helper.OnBoardGroup(userGroup, altGroupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchGroup -- Search group in authenticator, groupKey is the unique attribute of group in authenticator, for LDAP, the key is group DN
|
||||||
|
func SearchGroup(groupKey string) (*models.UserGroup, error) {
|
||||||
|
helper, err := getHelper()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return helper.SearchGroup(groupKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchAndOnBoardUser ... Search user and OnBoard user, if user exist, return the ID of current user.
|
||||||
|
func SearchAndOnBoardUser(username string) (int, error) {
|
||||||
|
user, err := SearchUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if user != nil {
|
||||||
|
err = OnBoardUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user.UserID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchAndOnBoardGroup ... if altGroupName is not empty, take the altGroupName as groupName in harbor DB
|
||||||
|
func SearchAndOnBoardGroup(groupKey, altGroupName string) (int, error) {
|
||||||
|
userGroup, err := SearchGroup(groupKey)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if userGroup != nil {
|
||||||
|
err = OnBoardGroup(userGroup, altGroupName)
|
||||||
|
}
|
||||||
|
return userGroup.ID, err
|
||||||
|
}
|
||||||
|
|
||||||
// PostAuthenticate -
|
// PostAuthenticate -
|
||||||
func PostAuthenticate(u *models.User) error {
|
func PostAuthenticate(u *models.User) error {
|
||||||
helper, err := getHelper()
|
helper, err := getHelper()
|
||||||
|
@ -126,7 +126,7 @@ func TestSearchUser(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthenticateHelperOnboardUser(t *testing.T) {
|
func TestAuthenticateHelperOnBoardUser(t *testing.T) {
|
||||||
user := models.User{
|
user := models.User{
|
||||||
Username: "test01",
|
Username: "test01",
|
||||||
Realname: "test01",
|
Realname: "test01",
|
||||||
|
@ -19,7 +19,10 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common"
|
||||||
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/dao/group"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
@ -130,6 +133,45 @@ func (l *Auth) SearchUser(username string) (*models.User, error) {
|
|||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//SearchGroup -- Search group in ldap authenticator, groupKey is LDAP group DN.
|
||||||
|
func (l *Auth) SearchGroup(groupKey string) (*models.UserGroup, error) {
|
||||||
|
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can not load system ldap config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ldapSession.Open(); err != nil {
|
||||||
|
log.Warningf("ldap connection fail: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ldapSession.Close()
|
||||||
|
userGroupList, err := ldapSession.SearchGroupByDN(groupKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("ldap search group fail: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(userGroupList) == 0 {
|
||||||
|
return nil, fmt.Errorf("Failed to searh ldap group with groupDN:%v", groupKey)
|
||||||
|
}
|
||||||
|
userGroup := models.UserGroup{
|
||||||
|
GroupName: userGroupList[0].GroupName,
|
||||||
|
LdapGroupDN: userGroupList[0].GroupDN,
|
||||||
|
}
|
||||||
|
return &userGroup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnBoardGroup -- Create Group in harbor DB, if altGroupName is not empty, take the altGroupName as groupName in harbor DB.
|
||||||
|
func (l *Auth) OnBoardGroup(u *models.UserGroup, altGroupName string) error {
|
||||||
|
if len(altGroupName) > 0 {
|
||||||
|
u.GroupName = altGroupName
|
||||||
|
}
|
||||||
|
u.GroupType = common.LdapGroupType
|
||||||
|
return group.OnBoardUserGroup(u, "LdapGroupDN", "GroupType")
|
||||||
|
}
|
||||||
|
|
||||||
//PostAuthenticate -- If user exist in harbor DB, sync email address, if not exist, call OnBoardUser
|
//PostAuthenticate -- If user exist in harbor DB, sync email address, if not exist, call OnBoardUser
|
||||||
func (l *Auth) PostAuthenticate(u *models.User) error {
|
func (l *Auth) PostAuthenticate(u *models.User) error {
|
||||||
|
|
||||||
|
@ -22,9 +22,11 @@ import (
|
|||||||
|
|
||||||
"github.com/vmware/harbor/src/common"
|
"github.com/vmware/harbor/src/common"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
|
"github.com/vmware/harbor/src/common/dao/project"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/common/utils/test"
|
"github.com/vmware/harbor/src/common/utils/test"
|
||||||
|
"github.com/vmware/harbor/src/ui/api"
|
||||||
"github.com/vmware/harbor/src/ui/auth"
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
uiConfig "github.com/vmware/harbor/src/ui/config"
|
uiConfig "github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
@ -46,7 +48,7 @@ var adminServerLdapTestConfig = map[string]interface{}{
|
|||||||
common.LDAPBaseDN: "dc=example,dc=com",
|
common.LDAPBaseDN: "dc=example,dc=com",
|
||||||
common.LDAPUID: "uid",
|
common.LDAPUID: "uid",
|
||||||
common.LDAPFilter: "",
|
common.LDAPFilter: "",
|
||||||
common.LDAPScope: 3,
|
common.LDAPScope: 2,
|
||||||
common.LDAPTimeout: 30,
|
common.LDAPTimeout: 30,
|
||||||
// config.TokenServiceURL: "",
|
// config.TokenServiceURL: "",
|
||||||
// config.RegistryURL: "",
|
// config.RegistryURL: "",
|
||||||
@ -64,6 +66,10 @@ var adminServerLdapTestConfig = map[string]interface{}{
|
|||||||
common.CfgExpiration: 5,
|
common.CfgExpiration: 5,
|
||||||
// config.JobLogDir: "/var/log/jobs",
|
// config.JobLogDir: "/var/log/jobs",
|
||||||
common.AdminInitialPassword: "password",
|
common.AdminInitialPassword: "password",
|
||||||
|
common.LDAPGroupSearchFilter: "objectclass=groupOfNames",
|
||||||
|
common.LDAPGroupBaseDN: "dc=example,dc=com",
|
||||||
|
common.LDAPGroupAttributeName: "cn",
|
||||||
|
common.LDAPGroupSearchScope: 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@ -102,6 +108,24 @@ func TestMain(m *testing.M) {
|
|||||||
log.Fatalf("failed to initialize database: %v", err)
|
log.Fatalf("failed to initialize database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Extract to test utils
|
||||||
|
initSqls := []string{
|
||||||
|
"insert into user (username, email, password, realname) values ('member_test_01', 'member_test_01@example.com', '123456', 'member_test_01')",
|
||||||
|
"insert into project (name, owner_id) values ('member_test_01', 1)",
|
||||||
|
"insert into user_group (group_name, group_type, group_property) values ('test_group_01', 1, 'CN=harbor_users,OU=sample,OU=vmware,DC=harbor,DC=com')",
|
||||||
|
"update project set owner_id = (select user_id from user where username = 'member_test_01') where name = 'member_test_01'",
|
||||||
|
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_01') , (select user_id from user where username = 'member_test_01'), 'u', 1)",
|
||||||
|
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_01') , (select id from user_group where group_name = 'test_group_01'), 'g', 1)",
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSqls := []string{
|
||||||
|
"delete from project where name='member_test_01'",
|
||||||
|
"delete from user where username='member_test_01' or username='pm_sample'",
|
||||||
|
"delete from user_group",
|
||||||
|
"delete from project_member",
|
||||||
|
}
|
||||||
|
dao.PrepareTestData(clearSqls, initSqls)
|
||||||
|
|
||||||
retCode := m.Run()
|
retCode := m.Run()
|
||||||
os.Exit(retCode)
|
os.Exit(retCode)
|
||||||
}
|
}
|
||||||
@ -168,7 +192,7 @@ func TestSearchUser_02(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnboardUser(t *testing.T) {
|
func TestOnBoardUser(t *testing.T) {
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Username: "sample",
|
Username: "sample",
|
||||||
Email: "sample@example.com",
|
Email: "sample@example.com",
|
||||||
@ -186,7 +210,7 @@ func TestOnboardUser(t *testing.T) {
|
|||||||
assert.Equal(t, "sample@example.com", user.Email)
|
assert.Equal(t, "sample@example.com", user.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnboardUser_02(t *testing.T) {
|
func TestOnBoardUser_02(t *testing.T) {
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Username: "sample02",
|
Username: "sample02",
|
||||||
Realname: "Sample02",
|
Realname: "Sample02",
|
||||||
@ -204,7 +228,7 @@ func TestOnboardUser_02(t *testing.T) {
|
|||||||
dao.CleanUser(int64(user.UserID))
|
dao.CleanUser(int64(user.UserID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnboardUser_03(t *testing.T) {
|
func TestOnBoardUser_03(t *testing.T) {
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Username: "sample03@example.com",
|
Username: "sample03@example.com",
|
||||||
Realname: "Sample03",
|
Realname: "Sample03",
|
||||||
@ -222,7 +246,7 @@ func TestOnboardUser_03(t *testing.T) {
|
|||||||
dao.CleanUser(int64(user.UserID))
|
dao.CleanUser(int64(user.UserID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthenticateHelperOnboardUser(t *testing.T) {
|
func TestAuthenticateHelperOnBoardUser(t *testing.T) {
|
||||||
user := models.User{
|
user := models.User{
|
||||||
Username: "test01",
|
Username: "test01",
|
||||||
Realname: "test01",
|
Realname: "test01",
|
||||||
@ -240,6 +264,21 @@ func TestAuthenticateHelperOnboardUser(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOnBoardGroup(t *testing.T) {
|
||||||
|
group := models.UserGroup{
|
||||||
|
GroupName: "harbor_group2",
|
||||||
|
LdapGroupDN: "cn=harbor_group2,ou=groups,dc=example,dc=com",
|
||||||
|
}
|
||||||
|
newGroupName := "group_name123"
|
||||||
|
err := auth.OnBoardGroup(&group, newGroupName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to OnBoardGroup, %+v", group)
|
||||||
|
}
|
||||||
|
if group.GroupName != "group_name123" {
|
||||||
|
t.Errorf("The OnBoardGroup should have name %v", newGroupName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuthenticateHelperSearchUser(t *testing.T) {
|
func TestAuthenticateHelperSearchUser(t *testing.T) {
|
||||||
|
|
||||||
user, err := auth.SearchUser("test")
|
user, err := auth.SearchUser("test")
|
||||||
@ -307,3 +346,70 @@ func TestPostAuthentication(t *testing.T) {
|
|||||||
assert.EqualValues("test003@example.com", dbUser.Email)
|
assert.EqualValues("test003@example.com", dbUser.Email)
|
||||||
dao.CleanUser(int64(dbUser.UserID))
|
dao.CleanUser(int64(dbUser.UserID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchAndOnBoardUser(t *testing.T) {
|
||||||
|
userID, err := auth.SearchAndOnBoardUser("mike02")
|
||||||
|
defer dao.CleanUser(int64(userID))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when SearchAndOnBoardUser: %v", err)
|
||||||
|
}
|
||||||
|
if userID == 0 {
|
||||||
|
t.Errorf("Can not search and onboard user %v", "mike")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestAddOrUpdateProjectMemberWithLdapUser(t *testing.T) {
|
||||||
|
|
||||||
|
currentProject, err := dao.GetProjectByName("member_test_01")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when GetProjectByName: %v", err)
|
||||||
|
}
|
||||||
|
member := models.MemberReq{
|
||||||
|
ProjectID: currentProject.ProjectID,
|
||||||
|
MemberUser: models.User{
|
||||||
|
Username: "mike",
|
||||||
|
},
|
||||||
|
Role: models.PROJECTADMIN,
|
||||||
|
}
|
||||||
|
|
||||||
|
pmid, err := api.AddOrUpdateProjectMember(currentProject.ProjectID, member)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred in AddOrUpdateProjectMember: %v", err)
|
||||||
|
}
|
||||||
|
if pmid == 0 {
|
||||||
|
t.Errorf("Error occurred in AddOrUpdateProjectMember: pmid:%v", pmid)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestAddProjectMemberWithLdapGroup(t *testing.T) {
|
||||||
|
|
||||||
|
currentProject, err := dao.GetProjectByName("member_test_01")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred when GetProjectByName: %v", err)
|
||||||
|
}
|
||||||
|
member := models.MemberReq{
|
||||||
|
ProjectID: currentProject.ProjectID,
|
||||||
|
MemberGroup: models.UserGroup{
|
||||||
|
LdapGroupDN: "cn=harbor_users,ou=groups,dc=example,dc=com",
|
||||||
|
},
|
||||||
|
Role: models.PROJECTADMIN,
|
||||||
|
}
|
||||||
|
|
||||||
|
pmid, err := api.AddOrUpdateProjectMember(currentProject.ProjectID, member)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error occurred in AddOrUpdateProjectMember: %v", err)
|
||||||
|
}
|
||||||
|
if pmid == 0 {
|
||||||
|
t.Errorf("Error occurred in AddOrUpdateProjectMember: pmid: %v", pmid)
|
||||||
|
}
|
||||||
|
queryMember := models.Member{
|
||||||
|
ProjectID: currentProject.ProjectID,
|
||||||
|
}
|
||||||
|
memberList, err := project.GetProjectMember(queryMember)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to query project member, %v, error: %v", queryMember, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(memberList) == 0 {
|
||||||
|
t.Errorf("Failed to query project member, %v", queryMember)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -47,6 +47,7 @@ func initRouters() {
|
|||||||
|
|
||||||
//API:
|
//API:
|
||||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectUserMemberAPI{})
|
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectUserMemberAPI{})
|
||||||
|
beego.Router("/api/projects/:pid([0-9]+)/projectmembers/?:pmid([0-9]+)", &api.ProjectMemberAPI{})
|
||||||
beego.Router("/api/projects/", &api.ProjectAPI{}, "head:Head")
|
beego.Router("/api/projects/", &api.ProjectAPI{}, "head:Head")
|
||||||
beego.Router("/api/projects/:id([0-9]+)", &api.ProjectAPI{})
|
beego.Router("/api/projects/:id([0-9]+)", &api.ProjectAPI{})
|
||||||
|
|
||||||
@ -54,8 +55,10 @@ func initRouters() {
|
|||||||
beego.Router("/api/users", &api.UserAPI{}, "get:List;post:Post")
|
beego.Router("/api/users", &api.UserAPI{}, "get:List;post:Post")
|
||||||
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
||||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||||
|
beego.Router("/api/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{})
|
||||||
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
||||||
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "post:Search")
|
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "get:Search")
|
||||||
|
beego.Router("/api/ldap/groups/search", &api.LdapAPI{}, "get:SearchGroup")
|
||||||
beego.Router("/api/ldap/users/import", &api.LdapAPI{}, "post:ImportUser")
|
beego.Router("/api/ldap/users/import", &api.LdapAPI{}, "post:ImportUser")
|
||||||
beego.Router("/api/email/ping", &api.EmailAPI{}, "post:Ping")
|
beego.Router("/api/email/ping", &api.EmailAPI{}, "post:Ping")
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,24 @@ member: cn=mike05,ou=people,dc=example,dc=com
|
|||||||
objectclass: groupOfNames
|
objectclass: groupOfNames
|
||||||
objectclass: top
|
objectclass: top
|
||||||
|
|
||||||
|
# Group Entry harbor_group
|
||||||
|
dn: cn=harbor_group,ou=groups,dc=example,dc=com
|
||||||
|
cn: harbor_group
|
||||||
|
description: harbor group
|
||||||
|
member: cn=mike,ou=people,dc=example,dc=com
|
||||||
|
member: cn=mike02,ou=people,dc=example,dc=com
|
||||||
|
objectclass: groupOfNames
|
||||||
|
objectclass: top
|
||||||
|
|
||||||
|
# Group Entry harbor_group2
|
||||||
|
dn: cn=harbor_group2,ou=groups,dc=example,dc=com
|
||||||
|
cn: harbor_group2
|
||||||
|
description: harbor group2
|
||||||
|
member: cn=mike,ou=people,dc=example,dc=com
|
||||||
|
member: cn=mike02,ou=people,dc=example,dc=com
|
||||||
|
objectclass: groupOfNames
|
||||||
|
objectclass: top
|
||||||
|
|
||||||
# User belongs to harbor_user
|
# User belongs to harbor_user
|
||||||
dn: cn=mike,ou=people,dc=example,dc=com
|
dn: cn=mike,ou=people,dc=example,dc=com
|
||||||
cn: mike
|
cn: mike
|
||||||
|
Loading…
Reference in New Issue
Block a user