Browse Source

Fixes #7023 - API Org Visibility (#7028)

tags/v1.9.0-rc1
Richard Mahn 5 years ago
parent
commit
43cf2f3b55

+ 86
- 0
integrations/api_admin_org_test.go View File

// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
"net/http"
"net/url"
"strings"
"testing"

"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"

"github.com/stretchr/testify/assert"
)

func TestAPIAdminOrgCreate(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session)

var org = api.CreateOrgOption{
UserName: "user2_org",
FullName: "User2's organization",
Description: "This organization created by admin for user2",
Website: "https://try.gitea.io",
Location: "Shanghai",
Visibility: "private",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org)
resp := session.MakeRequest(t, req, http.StatusCreated)

var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg)

assert.Equal(t, org.UserName, apiOrg.UserName)
assert.Equal(t, org.FullName, apiOrg.FullName)
assert.Equal(t, org.Description, apiOrg.Description)
assert.Equal(t, org.Website, apiOrg.Website)
assert.Equal(t, org.Location, apiOrg.Location)
assert.Equal(t, org.Visibility, apiOrg.Visibility)

models.AssertExistsAndLoadBean(t, &models.User{
Name: org.UserName,
LowerName: strings.ToLower(org.UserName),
FullName: org.FullName,
})
})
}

func TestAPIAdminOrgCreateBadVisibility(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session)

var org = api.CreateOrgOption{
UserName: "user2_org",
FullName: "User2's organization",
Description: "This organization created by admin for user2",
Website: "https://try.gitea.io",
Location: "Shanghai",
Visibility: "notvalid",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org)
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
})
}

func TestAPIAdminOrgCreateNotAdmin(t *testing.T) {
prepareTestEnv(t)
nonAdminUsername := "user2"
session := loginUser(t, nonAdminUsername)
token := getTokenForLoggedInUser(t, session)
var org = api.CreateOrgOption{
UserName: "user2_org",
FullName: "User2's organization",
Description: "This organization created by admin for user2",
Website: "https://try.gitea.io",
Location: "Shanghai",
Visibility: "public",
}
req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org)
session.MakeRequest(t, req, http.StatusForbidden)
}

+ 47
- 1
integrations/api_org_test.go View File

"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )


func TestAPIOrg(t *testing.T) {
func TestAPIOrgCreate(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) { onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1") session := loginUser(t, "user1")


Description: "This organization created by user1", Description: "This organization created by user1",
Website: "https://try.gitea.io", Website: "https://try.gitea.io",
Location: "Shanghai", Location: "Shanghai",
Visibility: "limited",
} }
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &org) req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &org)
resp := session.MakeRequest(t, req, http.StatusCreated) resp := session.MakeRequest(t, req, http.StatusCreated)
assert.Equal(t, org.Description, apiOrg.Description) assert.Equal(t, org.Description, apiOrg.Description)
assert.Equal(t, org.Website, apiOrg.Website) assert.Equal(t, org.Website, apiOrg.Website)
assert.Equal(t, org.Location, apiOrg.Location) assert.Equal(t, org.Location, apiOrg.Location)
assert.Equal(t, org.Visibility, apiOrg.Visibility)


models.AssertExistsAndLoadBean(t, &models.User{ models.AssertExistsAndLoadBean(t, &models.User{
Name: org.UserName, Name: org.UserName,
}) })
} }


func TestAPIOrgEdit(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")

token := getTokenForLoggedInUser(t, session)
var org = api.EditOrgOption{
FullName: "User3 organization new full name",
Description: "A new description",
Website: "https://try.gitea.io/new",
Location: "Beijing",
Visibility: "private",
}
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user3?token="+token, &org)
resp := session.MakeRequest(t, req, http.StatusOK)

var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg)

assert.Equal(t, "user3", apiOrg.UserName)
assert.Equal(t, org.FullName, apiOrg.FullName)
assert.Equal(t, org.Description, apiOrg.Description)
assert.Equal(t, org.Website, apiOrg.Website)
assert.Equal(t, org.Location, apiOrg.Location)
assert.Equal(t, org.Visibility, apiOrg.Visibility)
})
}

func TestAPIOrgEditBadVisibility(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1")

token := getTokenForLoggedInUser(t, session)
var org = api.EditOrgOption{
FullName: "User3 organization new full name",
Description: "A new description",
Website: "https://try.gitea.io/new",
Location: "Beijing",
Visibility: "badvisibility",
}
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user3?token="+token, &org)
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
})
}

func TestAPIOrgDeny(t *testing.T) { func TestAPIOrgDeny(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) { onGiteaRun(t, func(*testing.T, *url.URL) {
setting.Service.RequireSignInView = true setting.Service.RequireSignInView = true

+ 2
- 0
integrations/api_user_orgs_test.go View File

Description: "", Description: "",
Website: "", Website: "",
Location: "", Location: "",
Visibility: "public",
}, },
}, orgs) }, orgs)
} }
Description: "", Description: "",
Website: "", Website: "",
Location: "", Location: "",
Visibility: "public",
}, },
}, orgs) }, orgs)
} }

+ 19
- 14
modules/structs/org.go View File



// Organization represents an organization // Organization represents an organization
type Organization struct { type Organization struct {
ID int64 `json:"id"`
UserName string `json:"username"`
FullName string `json:"full_name"`
AvatarURL string `json:"avatar_url"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility VisibleType `json:"visibility"`
ID int64 `json:"id"`
UserName string `json:"username"`
FullName string `json:"full_name"`
AvatarURL string `json:"avatar_url"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility string `json:"visibility"`
} }


// CreateOrgOption options for creating an organization // CreateOrgOption options for creating an organization
type CreateOrgOption struct { type CreateOrgOption struct {
// required: true // required: true
UserName string `json:"username" binding:"Required"`
FullName string `json:"full_name"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility VisibleType `json:"visibility"`
UserName string `json:"username" binding:"Required"`
FullName string `json:"full_name"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
// possible values are `public` (default), `limited` or `private`
// enum: public,limited,private
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
} }


// EditOrgOption options for editing an organization // EditOrgOption options for editing an organization
Description string `json:"description"` Description string `json:"description"`
Website string `json:"website"` Website string `json:"website"`
Location string `json:"location"` Location string `json:"location"`
// possible values are `public`, `limited` or `private`
// enum: public,limited,private
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
} }

+ 10
- 0
modules/structs/org_type.go View File

return vt == VisibleTypePrivate return vt == VisibleTypePrivate
} }


// VisibilityString provides the mode string of the visibility type (public, limited, private)
func (vt VisibleType) String() string {
for k, v := range VisibilityModes {
if vt == v {
return k
}
}
return ""
}

// ExtractKeysFromMapString provides a slice of keys from map // ExtractKeysFromMapString provides a slice of keys from map
func ExtractKeysFromMapString(in map[string]VisibleType) (keys []string) { func ExtractKeysFromMapString(in map[string]VisibleType) (keys []string) {
for k := range in { for k := range in {

+ 20
- 6
modules/structs/repo_file.go View File



// FileOptions options for all file APIs // FileOptions options for all file APIs
type FileOptions struct { type FileOptions struct {
Message string `json:"message" binding:"Required"`
BranchName string `json:"branch"`
NewBranchName string `json:"new_branch"`
Author Identity `json:"author"`
Committer Identity `json:"committer"`
// message (optional) for the commit of this file. if not supplied, a default message will be used
Message string `json:"message" binding:"Required"`
// branch (optional) to base this file from. if not given, the default branch is used
BranchName string `json:"branch"`
// new_branch (optional) will make a new branch from `branch` before creating the file
NewBranchName string `json:"new_branch"`
// `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
Author Identity `json:"author"`
Committer Identity `json:"committer"`
} }


// CreateFileOptions options for creating files // CreateFileOptions options for creating files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type CreateFileOptions struct { type CreateFileOptions struct {
FileOptions FileOptions
// content must be base64 encoded
// required: true
Content string `json:"content"` Content string `json:"content"`
} }


// DeleteFileOptions options for deleting files (used for other File structs below) // DeleteFileOptions options for deleting files (used for other File structs below)
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type DeleteFileOptions struct { type DeleteFileOptions struct {
FileOptions FileOptions
// sha is the SHA for the file that already exists
// required: true
SHA string `json:"sha" binding:"Required"` SHA string `json:"sha" binding:"Required"`
} }


// UpdateFileOptions options for updating files // UpdateFileOptions options for updating files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type UpdateFileOptions struct { type UpdateFileOptions struct {
DeleteFileOptions DeleteFileOptions
Content string `json:"content"`
// content must be base64 encoded
// required: true
Content string `json:"content"`
// from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL
FromPath string `json:"from_path" binding:"MaxSize(500)"` FromPath string `json:"from_path" binding:"MaxSize(500)"`
} }



+ 7
- 0
routers/api/v1/admin/org.go View File

return return
} }


visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[form.Visibility]
}

org := &models.User{ org := &models.User{
Name: form.UserName, Name: form.UserName,
FullName: form.FullName, FullName: form.FullName,
Location: form.Location, Location: form.Location,
IsActive: true, IsActive: true,
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
Visibility: visibility,
} }

if err := models.CreateOrganization(org, u); err != nil { if err := models.CreateOrganization(org, u); err != nil {
if models.IsErrUserAlreadyExist(err) || if models.IsErrUserAlreadyExist(err) ||
models.IsErrNameReserved(err) || models.IsErrNameReserved(err) ||

+ 1
- 0
routers/api/v1/convert/convert.go View File

Description: org.Description, Description: org.Description,
Website: org.Website, Website: org.Website,
Location: org.Location, Location: org.Location,
Visibility: org.Visibility.String(),
} }
} }



+ 12
- 2
routers/api/v1/org/org.go View File

return return
} }


visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[form.Visibility]
}

org := &models.User{ org := &models.User{
Name: form.UserName, Name: form.UserName,
FullName: form.FullName, FullName: form.FullName,
Location: form.Location, Location: form.Location,
IsActive: true, IsActive: true,
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
Visibility: visibility,
} }
if err := models.CreateOrganization(org, ctx.User); err != nil { if err := models.CreateOrganization(org, ctx.User); err != nil {
if models.IsErrUserAlreadyExist(err) || if models.IsErrUserAlreadyExist(err) ||
// required: true // required: true
// - name: body // - name: body
// in: body // in: body
// required: true
// schema: // schema:
// "$ref": "#/definitions/EditOrgOption" // "$ref": "#/definitions/EditOrgOption"
// responses: // responses:
org.Description = form.Description org.Description = form.Description
org.Website = form.Website org.Website = form.Website
org.Location = form.Location org.Location = form.Location
if err := models.UpdateUserCols(org, "full_name", "description", "website", "location"); err != nil {
ctx.Error(500, "UpdateUser", err)
if form.Visibility != "" {
org.Visibility = api.VisibilityModes[form.Visibility]
}
if err := models.UpdateUserCols(org, "full_name", "description", "website", "location", "visibility"); err != nil {
ctx.Error(500, "EditOrganization", err)
return return
} }



+ 3
- 3
routers/api/v1/repo/file.go View File

// required: true // required: true
// - name: body // - name: body
// in: body // in: body
// description: "'content' must be base64 encoded\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'sha' is the SHA for the file that already exists\n\n 'new_branch' (optional) will make a new branch from 'branch' before creating the file"
// required: true
// schema: // schema:
// "$ref": "#/definitions/CreateFileOptions" // "$ref": "#/definitions/CreateFileOptions"
// responses: // responses:
// required: true // required: true
// - name: body // - name: body
// in: body // in: body
// description: "'content' must be base64 encoded\n\n 'sha' is the SHA for the file that already exists\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before updating the file"
// required: true
// schema: // schema:
// "$ref": "#/definitions/UpdateFileOptions" // "$ref": "#/definitions/UpdateFileOptions"
// responses: // responses:
// required: true // required: true
// - name: body // - name: body
// in: body // in: body
// description: "'sha' is the SHA for the file to be deleted\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before deleting the file"
// required: true
// schema: // schema:
// "$ref": "#/definitions/DeleteFileOptions" // "$ref": "#/definitions/DeleteFileOptions"
// responses: // responses:

+ 51
- 14
templates/swagger/v1_json.tmpl View File

{ {
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true,
"schema": { "schema": {
"$ref": "#/definitions/EditOrgOption" "$ref": "#/definitions/EditOrgOption"
} }
"required": true "required": true
}, },
{ {
"description": "'content' must be base64 encoded\n\n 'sha' is the SHA for the file that already exists\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before updating the file",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true,
"schema": { "schema": {
"$ref": "#/definitions/UpdateFileOptions" "$ref": "#/definitions/UpdateFileOptions"
} }
"required": true "required": true
}, },
{ {
"description": "'content' must be base64 encoded\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'sha' is the SHA for the file that already exists\n\n 'new_branch' (optional) will make a new branch from 'branch' before creating the file",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true,
"schema": { "schema": {
"$ref": "#/definitions/CreateFileOptions" "$ref": "#/definitions/CreateFileOptions"
} }
"required": true "required": true
}, },
{ {
"description": "'sha' is the SHA for the file to be deleted\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before deleting the file",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true,
"schema": { "schema": {
"$ref": "#/definitions/DeleteFileOptions" "$ref": "#/definitions/DeleteFileOptions"
} }
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
}, },
"CreateFileOptions": { "CreateFileOptions": {
"description": "CreateFileOptions options for creating files",
"description": "CreateFileOptions options for creating files\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)",
"type": "object", "type": "object",
"required": [
"content"
],
"properties": { "properties": {
"author": { "author": {
"$ref": "#/definitions/Identity" "$ref": "#/definitions/Identity"
}, },
"branch": { "branch": {
"description": "branch (optional) to base this file from. if not given, the default branch is used",
"type": "string", "type": "string",
"x-go-name": "BranchName" "x-go-name": "BranchName"
}, },
"$ref": "#/definitions/Identity" "$ref": "#/definitions/Identity"
}, },
"content": { "content": {
"description": "content must be base64 encoded",
"type": "string", "type": "string",
"x-go-name": "Content" "x-go-name": "Content"
}, },
"message": { "message": {
"description": "message (optional) for the commit of this file. if not supplied, a default message will be used",
"type": "string", "type": "string",
"x-go-name": "Message" "x-go-name": "Message"
}, },
"new_branch": { "new_branch": {
"description": "new_branch (optional) will make a new branch from `branch` before creating the file",
"type": "string", "type": "string",
"x-go-name": "NewBranchName" "x-go-name": "NewBranchName"
} }
"x-go-name": "UserName" "x-go-name": "UserName"
}, },
"visibility": { "visibility": {
"$ref": "#/definitions/VisibleType"
"description": "possible values are `public` (default), `limited` or `private`",
"type": "string",
"enum": [
"public",
"limited",
"private"
],
"x-go-name": "Visibility"
}, },
"website": { "website": {
"type": "string", "type": "string",
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
}, },
"DeleteFileOptions": { "DeleteFileOptions": {
"description": "DeleteFileOptions options for deleting files (used for other File structs below)",
"description": "DeleteFileOptions options for deleting files (used for other File structs below)\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)",
"type": "object", "type": "object",
"required": [
"sha"
],
"properties": { "properties": {
"author": { "author": {
"$ref": "#/definitions/Identity" "$ref": "#/definitions/Identity"
}, },
"branch": { "branch": {
"description": "branch (optional) to base this file from. if not given, the default branch is used",
"type": "string", "type": "string",
"x-go-name": "BranchName" "x-go-name": "BranchName"
}, },
"$ref": "#/definitions/Identity" "$ref": "#/definitions/Identity"
}, },
"message": { "message": {
"description": "message (optional) for the commit of this file. if not supplied, a default message will be used",
"type": "string", "type": "string",
"x-go-name": "Message" "x-go-name": "Message"
}, },
"new_branch": { "new_branch": {
"description": "new_branch (optional) will make a new branch from `branch` before creating the file",
"type": "string", "type": "string",
"x-go-name": "NewBranchName" "x-go-name": "NewBranchName"
}, },
"sha": { "sha": {
"description": "sha is the SHA for the file that already exists",
"type": "string", "type": "string",
"x-go-name": "SHA" "x-go-name": "SHA"
} }
"type": "string", "type": "string",
"x-go-name": "Location" "x-go-name": "Location"
}, },
"visibility": {
"description": "possible values are `public`, `limited` or `private`",
"type": "string",
"enum": [
"public",
"limited",
"private"
],
"x-go-name": "Visibility"
},
"website": { "website": {
"type": "string", "type": "string",
"x-go-name": "Website" "x-go-name": "Website"
"x-go-name": "UserName" "x-go-name": "UserName"
}, },
"visibility": { "visibility": {
"$ref": "#/definitions/VisibleType"
"type": "string",
"x-go-name": "Visibility"
}, },
"website": { "website": {
"type": "string", "type": "string",
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
}, },
"UpdateFileOptions": { "UpdateFileOptions": {
"description": "UpdateFileOptions options for updating files",
"description": "UpdateFileOptions options for updating files\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)",
"type": "object", "type": "object",
"required": [
"sha",
"content"
],
"properties": { "properties": {
"author": { "author": {
"$ref": "#/definitions/Identity" "$ref": "#/definitions/Identity"
}, },
"branch": { "branch": {
"description": "branch (optional) to base this file from. if not given, the default branch is used",
"type": "string", "type": "string",
"x-go-name": "BranchName" "x-go-name": "BranchName"
}, },
"$ref": "#/definitions/Identity" "$ref": "#/definitions/Identity"
}, },
"content": { "content": {
"description": "content must be base64 encoded",
"type": "string", "type": "string",
"x-go-name": "Content" "x-go-name": "Content"
}, },
"from_path": { "from_path": {
"description": "from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL",
"type": "string", "type": "string",
"x-go-name": "FromPath" "x-go-name": "FromPath"
}, },
"message": { "message": {
"description": "message (optional) for the commit of this file. if not supplied, a default message will be used",
"type": "string", "type": "string",
"x-go-name": "Message" "x-go-name": "Message"
}, },
"new_branch": { "new_branch": {
"description": "new_branch (optional) will make a new branch from `branch` before creating the file",
"type": "string", "type": "string",
"x-go-name": "NewBranchName" "x-go-name": "NewBranchName"
}, },
"sha": { "sha": {
"description": "sha is the SHA for the file that already exists",
"type": "string", "type": "string",
"x-go-name": "SHA" "x-go-name": "SHA"
} }
}, },
"x-go-package": "code.gitea.io/gitea/models" "x-go-package": "code.gitea.io/gitea/models"
}, },
"VisibleType": {
"description": "VisibleType defines the visibility (Organization only)",
"type": "integer",
"format": "int64",
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"WatchInfo": { "WatchInfo": {
"description": "WatchInfo represents an API watch status of one repository", "description": "WatchInfo represents an API watch status of one repository",
"type": "object", "type": "object",

Loading…
Cancel
Save