mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-08 01:04:47 +01:00
add rest endpoints and update openapi doc
This commit is contained in:
parent
9c5adfea2b
commit
de8608f99f
@ -6,6 +6,8 @@ info:
|
|||||||
tags:
|
tags:
|
||||||
- name: Users
|
- name: Users
|
||||||
description: Interact with and view information about users.
|
description: Interact with and view information about users.
|
||||||
|
- name: Tokens
|
||||||
|
description: Interact with and view information about tokens.
|
||||||
- name: Peers
|
- name: Peers
|
||||||
description: Interact with and view information about peers.
|
description: Interact with and view information about peers.
|
||||||
- name: Setup Keys
|
- name: Setup Keys
|
||||||
@ -284,6 +286,53 @@ components:
|
|||||||
- revoked
|
- revoked
|
||||||
- auto_groups
|
- auto_groups
|
||||||
- usage_limit
|
- usage_limit
|
||||||
|
PersonalAccessToken:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: ID of a token
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
description: Description of the token
|
||||||
|
type: string
|
||||||
|
# hashed_token:
|
||||||
|
# description: Hashed representation of the token
|
||||||
|
# type: string
|
||||||
|
expiration_date:
|
||||||
|
description: Date the token expires
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
created_by:
|
||||||
|
description: User ID of the user who created the token
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
description: Date the token was created
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
last_used:
|
||||||
|
description: Date the token was last used
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- description
|
||||||
|
# - hashed_token
|
||||||
|
- expiration_date
|
||||||
|
- created_by
|
||||||
|
- created_at
|
||||||
|
- last_used
|
||||||
|
PersonalAccessTokenRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
description: Description of the token
|
||||||
|
type: string
|
||||||
|
expires_in:
|
||||||
|
description: Expiration in days
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- description
|
||||||
|
- expires_in
|
||||||
GroupMinimum:
|
GroupMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -848,6 +897,133 @@ paths:
|
|||||||
"$ref": "#/components/responses/forbidden"
|
"$ref": "#/components/responses/forbidden"
|
||||||
'500':
|
'500':
|
||||||
"$ref": "#/components/responses/internal_error"
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/users/{userId}/tokens:
|
||||||
|
get:
|
||||||
|
summary: Returns a list of all tokens for a user
|
||||||
|
tags: [ Tokens ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: userId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The User ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A JSON Array of PersonalAccessTokens
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/PersonalAccessToken'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
post:
|
||||||
|
summary: Create a new token
|
||||||
|
tags: [ Tokens ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: userId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The User ID
|
||||||
|
requestBody:
|
||||||
|
description: PersonalAccessToken create parameters
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PersonalAccessTokenRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: The token in plain text
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/users/{userId}/tokens/{tokenId}:
|
||||||
|
get:
|
||||||
|
summary: Returns a specific token
|
||||||
|
tags: [ Tokens ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: userId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The User ID
|
||||||
|
- in: path
|
||||||
|
name: tokenId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Token ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A PersonalAccessTokens Object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PersonalAccessToken'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
delete:
|
||||||
|
summary: Delete a token
|
||||||
|
tags: [ Tokens ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: userId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The User ID
|
||||||
|
- in: path
|
||||||
|
name: tokenId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Token ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Delete status code
|
||||||
|
content: { }
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
/api/peers:
|
/api/peers:
|
||||||
get:
|
get:
|
||||||
summary: Returns a list of all peers
|
summary: Returns a list of all peers
|
||||||
|
@ -379,6 +379,36 @@ type PeerMinimum struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PersonalAccessToken defines model for PersonalAccessToken.
|
||||||
|
type PersonalAccessToken struct {
|
||||||
|
// CreatedAt Date the token was created
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
|
// CreatedBy User ID of the user who created the token
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
|
||||||
|
// Description Description of the token
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// ExpirationDate Date the token expires
|
||||||
|
ExpirationDate time.Time `json:"expiration_date"`
|
||||||
|
|
||||||
|
// Id ID of a token
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// LastUsed Date the token was last used
|
||||||
|
LastUsed time.Time `json:"last_used"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PersonalAccessTokenRequest defines model for PersonalAccessTokenRequest.
|
||||||
|
type PersonalAccessTokenRequest struct {
|
||||||
|
// Description Description of the token
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// ExpiresIn Expiration in days
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
// Policy defines model for Policy.
|
// Policy defines model for Policy.
|
||||||
type Policy struct {
|
type Policy struct {
|
||||||
// Description Policy friendly description
|
// Description Policy friendly description
|
||||||
@ -808,3 +838,6 @@ type PostApiUsersJSONRequestBody = UserCreateRequest
|
|||||||
|
|
||||||
// PutApiUsersIdJSONRequestBody defines body for PutApiUsersId for application/json ContentType.
|
// PutApiUsersIdJSONRequestBody defines body for PutApiUsersId for application/json ContentType.
|
||||||
type PutApiUsersIdJSONRequestBody = UserRequest
|
type PutApiUsersIdJSONRequestBody = UserRequest
|
||||||
|
|
||||||
|
// PostApiUsersUserIdTokensJSONRequestBody defines body for PostApiUsersUserIdTokens for application/json ContentType.
|
||||||
|
type PostApiUsersUserIdTokensJSONRequestBody = PersonalAccessTokenRequest
|
||||||
|
@ -57,6 +57,7 @@ func APIHandler(accountManager s.AccountManager, appMetrics telemetry.AppMetrics
|
|||||||
api.addAccountsEndpoint()
|
api.addAccountsEndpoint()
|
||||||
api.addPeersEndpoint()
|
api.addPeersEndpoint()
|
||||||
api.addUsersEndpoint()
|
api.addUsersEndpoint()
|
||||||
|
api.addUsersTokensEndpoint()
|
||||||
api.addSetupKeysEndpoint()
|
api.addSetupKeysEndpoint()
|
||||||
api.addRulesEndpoint()
|
api.addRulesEndpoint()
|
||||||
api.addPoliciesEndpoint()
|
api.addPoliciesEndpoint()
|
||||||
@ -110,6 +111,14 @@ func (apiHandler *apiHandler) addUsersEndpoint() {
|
|||||||
apiHandler.Router.HandleFunc("/users", userHandler.CreateUser).Methods("POST", "OPTIONS")
|
apiHandler.Router.HandleFunc("/users", userHandler.CreateUser).Methods("POST", "OPTIONS")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (apiHandler *apiHandler) addUsersTokensEndpoint() {
|
||||||
|
tokenHandler := NewPATsHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
|
||||||
|
apiHandler.Router.HandleFunc("/users/{userId}/tokens", tokenHandler.GetAllTokens).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.Router.HandleFunc("/users/{userId}/tokens", tokenHandler.CreateToken).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.Router.HandleFunc("/users/{userId}/tokens/{tokenId}", tokenHandler.GetToken).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.Router.HandleFunc("/users/{userId}/tokens/{tokenId}", tokenHandler.DeleteToken).Methods("DELETE", "OPTIONS")
|
||||||
|
}
|
||||||
|
|
||||||
func (apiHandler *apiHandler) addSetupKeysEndpoint() {
|
func (apiHandler *apiHandler) addSetupKeysEndpoint() {
|
||||||
keysHandler := NewSetupKeysHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
|
keysHandler := NewSetupKeysHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
|
||||||
apiHandler.Router.HandleFunc("/setup-keys", keysHandler.GetAllSetupKeys).Methods("GET", "OPTIONS")
|
apiHandler.Router.HandleFunc("/setup-keys", keysHandler.GetAllSetupKeys).Methods("GET", "OPTIONS")
|
||||||
|
187
management/server/http/pat_handler.go
Normal file
187
management/server/http/pat_handler.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PATHandler is the nameserver group handler of the account
|
||||||
|
type PATHandler struct {
|
||||||
|
accountManager server.AccountManager
|
||||||
|
claimsExtractor *jwtclaims.ClaimsExtractor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPATsHandler(accountManager server.AccountManager, authCfg AuthCfg) *PATHandler {
|
||||||
|
return &PATHandler{
|
||||||
|
accountManager: accountManager,
|
||||||
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||||
|
jwtclaims.WithAudience(authCfg.Audience),
|
||||||
|
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PATHandler) GetAllTokens(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
userID := vars["userId"]
|
||||||
|
if len(userID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userID != user.Id {
|
||||||
|
util.WriteErrorResponse("User not authorized to get tokens", http.StatusUnauthorized, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pats []*api.PersonalAccessToken
|
||||||
|
for _, pat := range account.Users[userID].PATs {
|
||||||
|
pats = append(pats, toPATResponse(pat))
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, pats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PATHandler) GetToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
userID := vars["userId"]
|
||||||
|
if len(userID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userID != user.Id {
|
||||||
|
util.WriteErrorResponse("User not authorized to get token", http.StatusUnauthorized, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenID := vars["tokenId"]
|
||||||
|
if len(tokenID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid token ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pat := account.Users[userID].PATs[tokenID]
|
||||||
|
util.WriteJSONObject(w, toPATResponse(pat))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PATHandler) CreateToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPut {
|
||||||
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
userID := vars["userId"]
|
||||||
|
if len(userID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userID != user.Id {
|
||||||
|
util.WriteErrorResponse("User not authorized to create token", http.StatusUnauthorized, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PostApiUsersUserIdTokensJSONRequestBody
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pat, plainToken, err := server.CreateNewPAT(req.Description, req.ExpiresIn, user.Id)
|
||||||
|
err = h.accountManager.AddPATToUser(account.Id, userID, pat)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, plainToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PATHandler) DeleteToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodDelete {
|
||||||
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
userID := vars["userId"]
|
||||||
|
if len(userID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userID != user.Id {
|
||||||
|
util.WriteErrorResponse("User not authorized to delete token", http.StatusUnauthorized, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenID := vars["tokenId"]
|
||||||
|
if len(tokenID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid token ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.DeletePAT(account.Id, userID, tokenID)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPATResponse(pat *server.PersonalAccessToken) *api.PersonalAccessToken {
|
||||||
|
return &api.PersonalAccessToken{
|
||||||
|
CreatedAt: pat.CreatedAt,
|
||||||
|
CreatedBy: pat.CreatedBy,
|
||||||
|
Description: pat.Description,
|
||||||
|
ExpirationDate: pat.ExpirationDate,
|
||||||
|
Id: pat.ID,
|
||||||
|
LastUsed: pat.LastUsed,
|
||||||
|
}
|
||||||
|
}
|
37
management/server/http/pat_handler_test.go
Normal file
37
management/server/http/pat_handler_test.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initPATTestData() *PATHandler {
|
||||||
|
return &PATHandler{
|
||||||
|
accountManager: &mock_server.MockAccountManager{
|
||||||
|
|
||||||
|
AddPATToUserFunc: func(accountID string, userID string, pat *server.PersonalAccessToken) error {
|
||||||
|
if nsGroupID == existingNSGroupID {
|
||||||
|
return baseExistingNSGroup.Copy(), nil
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(status.NotFound, "nameserver group with ID %s not found", nsGroupID)
|
||||||
|
},
|
||||||
|
|
||||||
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
|
return testingNSAccount, testingAccount.Users["test_user"], nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||||
|
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
|
||||||
|
return jwtclaims.AuthorizationClaims{
|
||||||
|
UserId: "test_user",
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
AccountId: testNSGroupAccountID,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user