Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization with access brood resource workflow #28

Merged
merged 2 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,16 @@ Executes `sign dropper` functionalities such as certifying drop claims for Dropp
With configuration file you can specify list of signers and sign drops with chosen one. Example of server configuration file `config.json`:

```json
[
{
"keyfile_path": "dev.json",
"password": "password.txt",
"password_type": "text_file"
}
]
{
"access_resource_id": "<access_brood_resource_uuid>",
"signers": [
{
"keyfile_path": "dev.json",
"password": "password.txt",
"password_type": "text_file"
}
]
}
```

Config also could be generated with command:
Expand Down
53 changes: 20 additions & 33 deletions bugout.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,62 +215,49 @@ func (c *BugoutAPIClient) GetUser(accessToken string) (User, error) {
return user, nil
}

type AccessWaggleResourceData struct {
Type string `json:"type"`
Customer string `json:"customer"`
AccessLevel string `json:"access_level"`
UserId string `json:"user_id"`
type ResourceHolder struct {
Id string `json:"id"`
HolderType string `json:"holder_type"`
Permissions []string `json:"permissions"`
}

type AccessWaggleResource struct {
Id string `json:"id"`
ApplicationId string `json:"application_id"`
ResourceData AccessWaggleResourceData `json:"resource_data"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
type ResourceHolders struct {
ResourceId string `json:"resource_id"`
Holders []ResourceHolder `json:"holders"`
}

type AccessWaggleResources struct {
Resources []AccessWaggleResource `json:"resources"`
}
func (c *BugoutAPIClient) CheckAccessToResource(token, resourceId string) (ResourceHolders, int, error) {
var resourceHolders ResourceHolders

func (c *BugoutAPIClient) GetAccessLevelFromResources() (AccessWaggleResources, error) {
var accessWaggleResources AccessWaggleResources
var requestBodyBytes []byte
request, requestErr := http.NewRequest("GET", fmt.Sprintf("%s/resources", c.BroodBaseURL), bytes.NewBuffer(requestBodyBytes))
request, requestErr := http.NewRequest("GET", fmt.Sprintf("%s/resources/%s/holders", c.BroodBaseURL, resourceId), bytes.NewBuffer(requestBodyBytes))
if requestErr != nil {
return accessWaggleResources, requestErr
return resourceHolders, 500, requestErr
}
queryParameters := request.URL.Query()
queryParameters.Add("application_id", MOONSTREAM_APPLICATION_ID)
queryParameters.Add("type", BUGOUT_RESOURCE_TYPE_WAGGLE_ACCESS)

request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", MOONSTREAM_WAGGLE_ADMIN_ACCESS_TOKEN))
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
request.Header.Add("Accept", "application/json")
request.Header.Add("Content-Type", "application/json")

response, responseErr := c.HTTPClient.Do(request)
if responseErr != nil {
return accessWaggleResources, responseErr
return resourceHolders, 500, responseErr
}
defer response.Body.Close()

responseBody, responseBodyErr := io.ReadAll(response.Body)

if response.StatusCode < 200 || response.StatusCode >= 300 {
if responseBodyErr != nil {
return accessWaggleResources, fmt.Errorf("unexpected status code: %d -- could not read response body: %s", response.StatusCode, responseBodyErr.Error())
}
if responseBodyErr != nil {
return resourceHolders, response.StatusCode, fmt.Errorf("could not read response body: %s", responseBodyErr.Error())
}

if responseBodyErr != nil {
return accessWaggleResources, fmt.Errorf("could not read response body: %s", responseBodyErr.Error())
if response.StatusCode < 200 || response.StatusCode >= 300 {
return resourceHolders, response.StatusCode, fmt.Errorf("unexpected status code: %d -- could not read response body: %s", response.StatusCode, response.Status)
}

unmarshalErr := json.Unmarshal(responseBody, &accessWaggleResources)
unmarshalErr := json.Unmarshal(responseBody, &resourceHolders)
if unmarshalErr != nil {
return accessWaggleResources, fmt.Errorf("could not parse response body: %s", unmarshalErr.Error())
return resourceHolders, response.StatusCode, fmt.Errorf("could not parse response body: %s", unmarshalErr.Error())
}

return accessWaggleResources, nil
return resourceHolders, response.StatusCode, nil
}
7 changes: 4 additions & 3 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,16 +532,16 @@ func CreateServerCommand() *cobra.Command {
Use: "run",
Short: "Run API server.",
RunE: func(cmd *cobra.Command, args []string) error {
configs, configsErr := ReadServerSignerConfig(config)
config, configsErr := ReadServerConfig(config)
if configsErr != nil {
return configsErr
}
if len(*configs) == 0 {
if len(config.Signers) == 0 {
return fmt.Errorf("no signers available")
}

availableSigners := make(map[string]AvailableSigner)
for _, c := range *configs {
for _, c := range config.Signers {
key, keyErr := KeyFromFile(c.KeyfilePath, c.Password)
if keyErr != nil {
return keyErr
Expand All @@ -567,6 +567,7 @@ func CreateServerCommand() *cobra.Command {
server := Server{
Host: host,
Port: port,
AccessResourceId: config.AccessResourceId,
AvailableSigners: availableSigners,
CORSWhitelist: corsWhitelist,
BugoutAPIClient: bugoutClient,
Expand Down
47 changes: 15 additions & 32 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type AvailableSigner struct {
type Server struct {
Host string
Port int
AccessResourceId string
AvailableSigners map[string]AvailableSigner
LogLevel int
CORSWhitelist map[string]bool
Expand Down Expand Up @@ -75,7 +76,6 @@ type AccessLevel struct {

type AuthorizationContext struct {
AuthorizationToken string
AccessLevel AccessLevel
}

// Check access id was provided correctly and save user access configuration to request context
Expand Down Expand Up @@ -105,46 +105,29 @@ func (server *Server) accessMiddleware(next http.Handler) http.Handler {
return
}

user, getUserErr := server.BugoutAPIClient.GetUser(authorizationToken)
if getUserErr != nil {
log.Println(getUserErr)
http.Error(w, "Access token not found", http.StatusNotFound)
return
}
if user.ApplicationId != MOONSTREAM_APPLICATION_ID {
http.Error(w, "Wrong bugout application", http.StatusForbidden)
return
}

accessWaggleResources, getAccessErr := server.BugoutAPIClient.GetAccessLevelFromResources()
if getAccessErr != nil {
log.Println(getAccessErr)
http.Error(w, "Internal server error", http.StatusInternalServerError)
access, statusCode, checkAccessErr := server.BugoutAPIClient.CheckAccessToResource(authorizationToken, server.AccessResourceId)
if checkAccessErr != nil {
log.Println(statusCode, checkAccessErr)
switch statusCode {
case 404:
http.Error(w, "Not Found", http.StatusNotFound)
case 400, 401, 403:
http.Error(w, "Unauthorized", http.StatusUnauthorized)
default:
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
return
}

var accessLevel AccessLevel
accessGranted := false
for _, resource := range accessWaggleResources.Resources {
if resource.ResourceData.UserId == user.Id {
if resource.ResourceData.AccessLevel == "admin" {
accessLevel.Admin = true
accessGranted = true
}
if resource.ResourceData.AccessLevel == "request_signatures" {
accessLevel.RequestSignatures = true
accessGranted = true
}
}
}
if !accessGranted {
if len(access.Holders) < 1 {
http.Error(w, "Access restricted", http.StatusForbidden)
return
}

// TODO(kompotkot): Add background task to fetch user ID and log it

authorizationContext := AuthorizationContext{
AuthorizationToken: authorizationToken,
AccessLevel: accessLevel,
}

ctxUser := context.WithValue(r.Context(), "authorizationContext", authorizationContext)
Expand Down
18 changes: 12 additions & 6 deletions settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ type ServerSignerConfig struct {
PasswordType string `json:"password_type"`
}

type ServerConfig struct {
AccessResourceId string `json:"access_resource_id"`
Signers []ServerSignerConfig `json:"signers"`
}

// PasswordType specifies available password types
type PasswordType string

Expand Down Expand Up @@ -140,8 +145,8 @@ func (ssc *ServerSignerConfig) ParseKeyfilePassword() (string, error) {
}

// ReadConfig parses list of configuration file paths to list of Application Probes configs
func ReadServerSignerConfig(rawConfigPath string) (*[]ServerSignerConfig, error) {
var configs []ServerSignerConfig
func ReadServerConfig(rawConfigPath string) (*ServerConfig, error) {
var config ServerConfig

configPath := strings.TrimSuffix(rawConfigPath, "/")
_, err := os.Stat(configPath)
Expand All @@ -156,13 +161,14 @@ func ReadServerSignerConfig(rawConfigPath string) (*[]ServerSignerConfig, error)
if err != nil {
log.Fatal(err)
}
configTemp := &[]ServerSignerConfig{}
configTemp := &ServerConfig{}
err = json.Unmarshal(rawBytes, configTemp)
if err != nil {
return nil, err
}

for _, ct := range *configTemp {
config.AccessResourceId = configTemp.AccessResourceId
for _, ct := range configTemp.Signers {
_, err := os.Stat(ct.KeyfilePath)
if err != nil {
if os.IsNotExist(err) {
Expand All @@ -176,12 +182,12 @@ func ReadServerSignerConfig(rawConfigPath string) (*[]ServerSignerConfig, error)
if passParseErr != nil {
continue
}
configs = append(configs, ServerSignerConfig{
config.Signers = append(config.Signers, ServerSignerConfig{
KeyfilePath: ct.KeyfilePath,
Password: password,
PasswordType: ct.PasswordType,
})
}

return &configs, nil
return &config, nil
}
Loading