mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-22 16:13:31 +01:00
add rest endpoints and update openapi doc
This commit is contained in:
parent
9c5adfea2b
commit
de8608f99f
@ -6,6 +6,8 @@ info:
|
||||
tags:
|
||||
- name: Users
|
||||
description: Interact with and view information about users.
|
||||
- name: Tokens
|
||||
description: Interact with and view information about tokens.
|
||||
- name: Peers
|
||||
description: Interact with and view information about peers.
|
||||
- name: Setup Keys
|
||||
@ -284,6 +286,53 @@ components:
|
||||
- revoked
|
||||
- auto_groups
|
||||
- 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:
|
||||
type: object
|
||||
properties:
|
||||
@ -848,6 +897,133 @@ paths:
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$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:
|
||||
get:
|
||||
summary: Returns a list of all peers
|
||||
|
@ -379,6 +379,36 @@ type PeerMinimum struct {
|
||||
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.
|
||||
type Policy struct {
|
||||
// Description Policy friendly description
|
||||
@ -808,3 +838,6 @@ type PostApiUsersJSONRequestBody = UserCreateRequest
|
||||
|
||||
// PutApiUsersIdJSONRequestBody defines body for PutApiUsersId for application/json ContentType.
|
||||
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.addPeersEndpoint()
|
||||
api.addUsersEndpoint()
|
||||
api.addUsersTokensEndpoint()
|
||||
api.addSetupKeysEndpoint()
|
||||
api.addRulesEndpoint()
|
||||
api.addPoliciesEndpoint()
|
||||
@ -110,6 +111,14 @@ func (apiHandler *apiHandler) addUsersEndpoint() {
|
||||
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() {
|
||||
keysHandler := NewSetupKeysHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
|
||||
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