mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 15:29:28 +01:00
API Tokens, Documentation
- API Tokens - More documentation - Update API
This commit is contained in:
parent
5d332266ac
commit
d6370c875e
8
database/sql_migrations/v1.2.0.sql
Normal file
8
database/sql_migrations/v1.2.0.sql
Normal file
@ -0,0 +1,8 @@
|
||||
create table `user__token` (
|
||||
`id` varchar(32) not null primary key,
|
||||
`created` datetime not null default NOW(),
|
||||
`expired` tinyint(1) not null default 0,
|
||||
`user_id` int(11) not null,
|
||||
|
||||
foreign key (`user_id`) references user(`id`) on delete cascade
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
18
models/APIToken.go
Normal file
18
models/APIToken.go
Normal file
@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
type APIToken struct {
|
||||
ID string `db:"id" json:"id"`
|
||||
Created time.Time `db:"created" json:"created"`
|
||||
Expired bool `db:"expired" json:"expired"`
|
||||
UserID int `db:"user_id" json:"user_id"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
database.Mysql.AddTableWithName(APIToken{}, "user__token").SetKeys(false, "id")
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
br
|
||||
|
||||
h4.text-center.text-muted Scheduled tasks are WIP
|
@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
@ -16,56 +17,67 @@ import (
|
||||
"gopkg.in/redis.v3"
|
||||
)
|
||||
|
||||
func resetSessionExpiry(sessionID string) {
|
||||
if err := database.Redis.Expire(sessionID, 7*24*time.Hour).Err(); err != nil {
|
||||
func resetSessionExpiry(sessionID string, ttl time.Duration) {
|
||||
var cmd *redis.BoolCmd
|
||||
|
||||
if ttl == 0 {
|
||||
cmd = database.Redis.Persist(sessionID)
|
||||
} else {
|
||||
cmd = database.Redis.Expire(sessionID, ttl)
|
||||
}
|
||||
|
||||
if err := cmd.Err(); err != nil {
|
||||
fmt.Println("Cannot reset session expiry:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func authentication(c *gin.Context) {
|
||||
cookie, err := c.Request.Cookie("semaphore")
|
||||
if err != nil {
|
||||
// create cookie
|
||||
new_cookie := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, new_cookie); err != nil {
|
||||
panic(err)
|
||||
var redisKey string
|
||||
ttl := 7 * 24 * time.Hour
|
||||
|
||||
if authHeader := strings.ToLower(c.Request.Header.Get("authorization")); len(authHeader) > 0 {
|
||||
redisKey = "token-session:" + strings.Replace(authHeader, "bearer ", "", 1)
|
||||
ttl = 0
|
||||
} else {
|
||||
cookie, err := c.Request.Cookie("semaphore")
|
||||
if err != nil {
|
||||
// create cookie
|
||||
new_cookie := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, new_cookie); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cookie_value := url.QueryEscape(base64.URLEncoding.EncodeToString(new_cookie))
|
||||
cookie = &http.Cookie{Name: "semaphore", Value: cookie_value, Path: "/", HttpOnly: true}
|
||||
http.SetCookie(c.Writer, cookie)
|
||||
}
|
||||
|
||||
cookie_value := url.QueryEscape(base64.URLEncoding.EncodeToString(new_cookie))
|
||||
cookie = &http.Cookie{Name: "semaphore", Value: cookie_value, Path: "/", HttpOnly: true}
|
||||
http.SetCookie(c.Writer, cookie)
|
||||
redisKey = "session:" + cookie.Value
|
||||
}
|
||||
|
||||
redis_key := "session:" + cookie.Value
|
||||
s, err := database.Redis.Get(redis_key).Result()
|
||||
s, err := database.Redis.Get(redisKey).Result()
|
||||
if err == redis.Nil {
|
||||
// create a session
|
||||
temp_session := models.Session{}
|
||||
s = string(temp_session.Encode())
|
||||
|
||||
if err := database.Redis.Set(redis_key, s, 0).Err(); err != nil {
|
||||
if err := database.Redis.Set(redisKey, s, 0).Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
fmt.Println("Cannot get cookie from redis:", err)
|
||||
fmt.Println("Cannot get session from redis:", err)
|
||||
c.AbortWithStatus(500)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// reset session expiry
|
||||
go resetSessionExpiry(redis_key)
|
||||
|
||||
sess, err := models.DecodeSession(cookie.Value, s)
|
||||
sess, err := models.DecodeSession(redisKey, s)
|
||||
if err != nil {
|
||||
fmt.Println("Cannot decode session:", err)
|
||||
util.AuthFailed(c)
|
||||
return
|
||||
}
|
||||
|
||||
sess.ID = cookie.Value
|
||||
c.Set("session", sess)
|
||||
|
||||
if sess.UserID != nil {
|
||||
user, err := models.FetchUser(*sess.UserID)
|
||||
if err != nil {
|
||||
@ -77,6 +89,10 @@ func authentication(c *gin.Context) {
|
||||
c.Set("user", user)
|
||||
}
|
||||
|
||||
// reset session expiry
|
||||
go resetSessionExpiry(redisKey, ttl)
|
||||
|
||||
c.Set("session", sess)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package routes
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
@ -14,7 +14,7 @@ import (
|
||||
sq "github.com/masterminds/squirrel"
|
||||
)
|
||||
|
||||
func Login(c *gin.Context) {
|
||||
func login(c *gin.Context) {
|
||||
var login struct {
|
||||
Auth string `json:"auth" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
@ -56,7 +56,7 @@ func Login(c *gin.Context) {
|
||||
session := c.MustGet("session").(models.Session)
|
||||
session.UserID = &user.ID
|
||||
|
||||
status := database.Redis.Set("session:"+session.ID, string(session.Encode()), 7*24*time.Hour)
|
||||
status := database.Redis.Set(session.ID, string(session.Encode()), 7*24*time.Hour)
|
||||
if err := status.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -64,9 +64,9 @@ func Login(c *gin.Context) {
|
||||
c.AbortWithStatus(204)
|
||||
}
|
||||
|
||||
func Logout(c *gin.Context) {
|
||||
func logout(c *gin.Context) {
|
||||
session := c.MustGet("session").(models.Session)
|
||||
if err := database.Redis.Del("session:" + session.ID).Err(); err != nil {
|
||||
if err := database.Redis.Del(session.ID).Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package projects
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
@ -9,6 +11,23 @@ import (
|
||||
)
|
||||
|
||||
func KeyMiddleware(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
keyID, err := util.GetIntParam("key_id", c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var key models.AccessKey
|
||||
if err := database.Mysql.SelectOne(&key, "select * from access_key where project_id=? and id=?", project.ID, keyID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Set("accessKey", key)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@ -25,7 +44,6 @@ func GetKeys(c *gin.Context) {
|
||||
}
|
||||
|
||||
query, args, _ := q.ToSql()
|
||||
|
||||
if _, err := database.Mysql.Select(&keys, query, args...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -57,17 +75,33 @@ func AddKey(c *gin.Context) {
|
||||
}
|
||||
|
||||
func UpdateKey(c *gin.Context) {
|
||||
c.AbortWithStatus(501)
|
||||
}
|
||||
var key models.AccessKey
|
||||
oldKey := c.MustGet("accessKey").(models.AccessKey)
|
||||
|
||||
func RemoveKey(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
keyID, err := util.GetIntParam("key_id", c)
|
||||
if err != nil {
|
||||
if err := c.Bind(&key); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := database.Mysql.Exec("delete from access_key where project_id=? and id=?", project.ID, keyID); err != nil {
|
||||
switch key.Type {
|
||||
case "aws", "gcloud", "do", "ssh":
|
||||
break
|
||||
default:
|
||||
c.AbortWithStatus(400)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := database.Mysql.Exec("update access_key set name=?, type=?, `key`=?, secret=?", key.Name, key.Type, key.Key, key.Secret, oldKey.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.AbortWithStatus(204)
|
||||
}
|
||||
|
||||
func RemoveKey(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
key := c.MustGet("accessKey").(models.AccessKey)
|
||||
|
||||
if _, err := database.Mysql.Exec("delete from access_key where project_id=? and id=?", project.ID, key.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package projects
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
@ -9,6 +11,23 @@ import (
|
||||
)
|
||||
|
||||
func RepositoryMiddleware(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
repositoryID, err := util.GetIntParam("repository_id", c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var repository models.Repository
|
||||
if err := database.Mysql.SelectOne(&repository, "select * from project__repository where project_id=? and id=?", project.ID, repositoryID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Set("repository", repository)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@ -44,15 +63,23 @@ func AddRepository(c *gin.Context) {
|
||||
}
|
||||
|
||||
func UpdateRepository(c *gin.Context) {
|
||||
c.AbortWithStatus(501)
|
||||
project := c.MustGet("project").(models.Project)
|
||||
var repository models.Repository
|
||||
|
||||
if err := c.Bind(&repository); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := database.Mysql.Exec("update project__repository set git_url=?, ssh_key_id=? where id=?", repository.GitUrl, repository.SshKeyID, c.MustGet("repository").(models.Repository).ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.AbortWithStatus(204)
|
||||
}
|
||||
|
||||
func RemoveRepository(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
repositoryID, err := util.GetIntParam("repository_id", c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
repository := c.MustGet("repository").(models.Repository)
|
||||
|
||||
if _, err := database.Mysql.Exec("delete from project__repository where project_id=? and id=?", project.ID, repositoryID); err != nil {
|
||||
panic(err)
|
||||
|
@ -1,6 +1,8 @@
|
||||
package projects
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
@ -9,6 +11,23 @@ import (
|
||||
)
|
||||
|
||||
func UserMiddleware(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
userID, err := util.GetIntParam("user_id", c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := database.Mysql.SelectOne(&user, "select u.* from project__user as pu join user as u on pu.user_id=u.id where pu.user_id=? and pu.project_id=?", userID, project.ID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Set("projectUser", user)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@ -49,12 +68,9 @@ func AddUser(c *gin.Context) {
|
||||
|
||||
func RemoveUser(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
userID, err := util.GetIntParam("user_id", c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
user := c.MustGet("projectUser").(models.User)
|
||||
|
||||
if _, err := database.Mysql.Exec("delete from project__user where user_id=? and project_id=?", userID, project.ID); err != nil {
|
||||
if _, err := database.Mysql.Exec("delete from project__user where user_id=? and project_id=?", user.ID, project.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -62,9 +78,18 @@ func RemoveUser(c *gin.Context) {
|
||||
}
|
||||
|
||||
func MakeUserAdmin(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
user := c.MustGet("projectUser").(models.User)
|
||||
admin := 1
|
||||
|
||||
if c.Request.Method == "DELETE" {
|
||||
// strip admin
|
||||
admin = 0
|
||||
}
|
||||
|
||||
c.AbortWithStatus(501)
|
||||
if _, err := database.Mysql.Exec("update project__user set admin=? where user_id=? and project_id=?", admin, user.ID, project.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.AbortWithStatus(204)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package routes
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/routes/auth"
|
||||
"github.com/ansible-semaphore/semaphore/routes/projects"
|
||||
"github.com/ansible-semaphore/semaphore/routes/sockets"
|
||||
"github.com/ansible-semaphore/semaphore/routes/tasks"
|
||||
@ -25,16 +24,22 @@ func Route(r *gin.Engine) {
|
||||
api.Use(authentication)
|
||||
|
||||
func(api *gin.RouterGroup) {
|
||||
api.POST("/login", auth.Login)
|
||||
api.POST("/logout", auth.Logout)
|
||||
api.POST("/login", login)
|
||||
api.POST("/logout", logout)
|
||||
}(api.Group("/auth"))
|
||||
|
||||
api.Use(MustAuthenticate)
|
||||
|
||||
api.GET("/ws", sockets.Handler)
|
||||
|
||||
api.GET("/user", getUser)
|
||||
// api.PUT("/user", misc.UpdateUser)
|
||||
func(api *gin.RouterGroup) {
|
||||
api.GET("", getUser)
|
||||
// api.PUT("/user", misc.UpdateUser)
|
||||
|
||||
api.GET("/tokens", getAPITokens)
|
||||
api.POST("/tokens", createAPIToken)
|
||||
api.DELETE("/tokens/:token_id", expireAPIToken)
|
||||
}(api.Group("/user"))
|
||||
|
||||
api.GET("/projects", projects.GetProjects)
|
||||
api.POST("/projects", projects.AddProject)
|
||||
@ -129,7 +134,3 @@ func servePublic(c *gin.Context) {
|
||||
c.Writer.Header().Set("content-type", contentType)
|
||||
c.String(200, string(res))
|
||||
}
|
||||
|
||||
func getUser(c *gin.Context) {
|
||||
c.JSON(200, c.MustGet("user"))
|
||||
}
|
||||
|
80
routes/user.go
Normal file
80
routes/user.go
Normal file
@ -0,0 +1,80 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func getUser(c *gin.Context) {
|
||||
c.JSON(200, c.MustGet("user"))
|
||||
}
|
||||
|
||||
func getAPITokens(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
var tokens []models.APIToken
|
||||
if _, err := database.Mysql.Select(&tokens, "select * from user__token where user_id=?", user.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.JSON(200, tokens)
|
||||
}
|
||||
|
||||
func createAPIToken(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
tokenID := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, tokenID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
token := models.APIToken{
|
||||
ID: strings.ToLower(base64.URLEncoding.EncodeToString(tokenID)),
|
||||
Created: time.Now(),
|
||||
UserID: user.ID,
|
||||
Expired: false,
|
||||
}
|
||||
|
||||
if err := database.Mysql.Insert(&token); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
temp_session := models.Session{
|
||||
UserID: &user.ID,
|
||||
}
|
||||
if err := database.Redis.Set("token-session:"+token.ID, temp_session.Encode(), 0).Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.JSON(201, token)
|
||||
}
|
||||
|
||||
func expireAPIToken(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
tokenID := c.Param("token_id")
|
||||
res, err := database.Mysql.Exec("update user__token set expired=1 where id=? and user_id=?", tokenID, user.ID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected > 0 {
|
||||
// remove from redis
|
||||
if err := database.Redis.Del("token-session:" + tokenID).Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
c.AbortWithStatus(204)
|
||||
}
|
227
swagger.yml
227
swagger.yml
@ -45,12 +45,52 @@ definitions:
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
APIToken:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
expired:
|
||||
type: boolean
|
||||
user_id:
|
||||
type: integer
|
||||
Project:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
securityDefinitions:
|
||||
cookie:
|
||||
type: apiKey
|
||||
name: Cookie
|
||||
in: header
|
||||
# securityDefinitions:
|
||||
# cookie:
|
||||
# type: apiKey
|
||||
# name: Cookie
|
||||
# in: header
|
||||
# bearer:
|
||||
# type: apiKey
|
||||
# name: Authorization
|
||||
# in: header
|
||||
|
||||
parameters:
|
||||
project_id:
|
||||
name: project_id
|
||||
description: Project ID
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
user_id:
|
||||
name: user_id
|
||||
description: User ID
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
|
||||
paths:
|
||||
/ping:
|
||||
@ -61,8 +101,25 @@ paths:
|
||||
description: Successful "PONG" reply
|
||||
schema:
|
||||
$ref: "#/definitions/PONG"
|
||||
|
||||
/ws:
|
||||
get:
|
||||
summary: Websocket handler
|
||||
schemes:
|
||||
- ws
|
||||
- wss
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
# security:
|
||||
# - cookie: []
|
||||
# - bearer: []
|
||||
|
||||
# Authentication
|
||||
/auth/login:
|
||||
post:
|
||||
tags:
|
||||
- authentication
|
||||
summary: Performs Login
|
||||
description: |
|
||||
Upon success you will be logged in
|
||||
@ -79,26 +136,162 @@ paths:
|
||||
description: something in body is missing / is invalid
|
||||
/auth/logout:
|
||||
post:
|
||||
tags:
|
||||
- authentication
|
||||
summary: Destroys current session
|
||||
responses:
|
||||
204:
|
||||
description: Your session was successfully nuked
|
||||
/ws:
|
||||
get:
|
||||
summary: Websocket handler
|
||||
schemes:
|
||||
- ws
|
||||
- wss
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
security:
|
||||
- cookie: []
|
||||
|
||||
# User stuff
|
||||
/user:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Fetch logged in user
|
||||
responses:
|
||||
200:
|
||||
description: User
|
||||
schema:
|
||||
$ref: "#/definitions/User"
|
||||
$ref: "#/definitions/User"
|
||||
|
||||
/user/tokens:
|
||||
get:
|
||||
tags:
|
||||
- authentication
|
||||
- user
|
||||
summary: Fetch API tokens for user
|
||||
responses:
|
||||
200:
|
||||
description: API Tokens
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/APIToken"
|
||||
post:
|
||||
tags:
|
||||
- authentication
|
||||
- user
|
||||
summary: Create an API token
|
||||
responses:
|
||||
201:
|
||||
description: API Token
|
||||
schema:
|
||||
$ref: "#/definitions/APIToken"
|
||||
/user/tokens/{api_token_id}:
|
||||
parameters:
|
||||
- name: api_token_id
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
delete:
|
||||
tags:
|
||||
- authentication
|
||||
- user
|
||||
summary: Expires API token
|
||||
responses:
|
||||
204:
|
||||
description: Expired API Token
|
||||
|
||||
# Projects
|
||||
/projects:
|
||||
get:
|
||||
tags:
|
||||
- projects
|
||||
summary: Get projects
|
||||
responses:
|
||||
200:
|
||||
description: List of projects
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/Project"
|
||||
post:
|
||||
tags:
|
||||
- projects
|
||||
summary: Create a new project
|
||||
parameters:
|
||||
- name: Project
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Project'
|
||||
responses:
|
||||
200:
|
||||
description: Created project
|
||||
/project/{project_id}:
|
||||
parameters:
|
||||
- $ref: "#/parameters/project_id"
|
||||
get:
|
||||
tags:
|
||||
- project
|
||||
summary: Fetch project
|
||||
responses:
|
||||
200:
|
||||
description: Project
|
||||
schema:
|
||||
$ref: "#/definitions/Project"
|
||||
|
||||
# User management
|
||||
/project/{project_id}/users:
|
||||
parameters:
|
||||
- $ref: "#/parameters/project_id"
|
||||
get:
|
||||
tags:
|
||||
- project
|
||||
summary: Get users linked to project
|
||||
responses:
|
||||
200:
|
||||
description: Users
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/User"
|
||||
post:
|
||||
tags:
|
||||
- project
|
||||
summary: Link user to project
|
||||
parameters:
|
||||
- name: User
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
user_id:
|
||||
type: integer
|
||||
format: userID
|
||||
admin:
|
||||
type: boolean
|
||||
responses:
|
||||
204:
|
||||
description: User added
|
||||
/project/{project_id}/users/{user_id}:
|
||||
parameters:
|
||||
- $ref: "#/parameters/project_id"
|
||||
- $ref: "#/parameters/user_id"
|
||||
delete:
|
||||
tags:
|
||||
- project
|
||||
summary: Removes user from project
|
||||
responses:
|
||||
204:
|
||||
description: User removed
|
||||
/project/{project_id}/users/{user_id}/admin:
|
||||
parameters:
|
||||
- $ref: "#/parameters/project_id"
|
||||
- $ref: "#/parameters/user_id"
|
||||
post:
|
||||
tags:
|
||||
- project
|
||||
summary: Makes user admin
|
||||
responses:
|
||||
204:
|
||||
description: User made administrator
|
||||
delete:
|
||||
tags:
|
||||
- project
|
||||
summary: Revoke admin privileges
|
||||
responses:
|
||||
204:
|
||||
description: User admin privileges revoked
|
||||
|
Loading…
Reference in New Issue
Block a user