...

Source file src/edge-infra.dev/pkg/edge/iam/profile/profile.go

Documentation: edge-infra.dev/pkg/edge/iam/profile

     1  package profile
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"net/http"
     9  	"time"
    10  
    11  	"edge-infra.dev/pkg/edge/iam/config"
    12  	"edge-infra.dev/pkg/edge/iam/util"
    13  )
    14  
    15  type Profile struct {
    16  	Subject      string `json:"sub"`
    17  	Organization string `json:"org"`
    18  	// Site         string        `json:"eu"` // todo: add
    19  	Roles       string        `json:"rls"`
    20  	GivenName   string        `json:"given_name,omitempty"`
    21  	FamilyName  string        `json:"family_name,omitempty"`
    22  	FullName    string        `json:"name,omitempty"`
    23  	Email       string        `json:"email,omitempty"`
    24  	Address     *AddressClaim `json:"address,omitempty"`
    25  	LastUpdated int64         `json:"last_updated"`
    26  	Alias       string        `json:"alias"`
    27  	DeviceLogin string        `json:"device_login,omitempty"`
    28  	Age         int           `json:"age,omitempty"`
    29  }
    30  
    31  type Address struct {
    32  	City       string `json:"city,omitempty"`
    33  	Country    string `json:"country,omitempty"`
    34  	Street     string `json:"street,omitempty"`
    35  	State      string `json:"state,omitempty"`
    36  	PostalCode string `json:"postalCode,omitempty"`
    37  }
    38  type ProvisioningUserProfileResponse struct {
    39  	GivenName  string   `json:"givenName,omitempty"`
    40  	FamilyName string   `json:"familyName,omitempty"`
    41  	FullName   string   `json:"fullName,omitempty"`
    42  	Email      string   `json:"email,omitempty"`
    43  	Address    *Address `json:"address,omitempty"`
    44  }
    45  
    46  func (p *Profile) RequireVerification(isOffline bool) (bool, error) {
    47  	lastUpdated := time.Unix(p.LastUpdated, 0)
    48  	duration := time.Since(lastUpdated)
    49  
    50  	// no verification needed
    51  	if duration < config.GetProfileLifespan() {
    52  		return false, nil
    53  	}
    54  
    55  	// skip verification during LAN outage
    56  	if isOffline {
    57  		return false, nil
    58  	}
    59  
    60  	// skip verification if can't reach bsl/okta (WAN outage)
    61  	client := &http.Client{
    62  		Timeout: config.CloudIDPTimeout(),
    63  	}
    64  
    65  	url := config.BslSecurityURL()
    66  	if config.OktaEnabled() {
    67  		url = config.OktaIssuer()
    68  	}
    69  	resp, err := client.Get(url)
    70  
    71  	// we timed-out, we're OK with skipping verification
    72  	if _, ok := err.(net.Error); ok {
    73  		return false, nil
    74  	}
    75  
    76  	// something else happened
    77  	if err != nil {
    78  		return true, err
    79  	}
    80  
    81  	defer resp.Body.Close()
    82  
    83  	return true, nil
    84  }
    85  
    86  // GetUserProfile from the provisioning user-profiles service
    87  func GetUserProfile(accessToken string) (*ProvisioningUserProfileResponse, error) {
    88  	url := config.ProvisioningUserProfilesURL()
    89  
    90  	req, err := http.NewRequest("GET", url, nil)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	// set required headers
    96  	req.Header.Set("nep-organization", config.OrganizationName())
    97  	req.Header.Set("Authorization", "AccessToken "+accessToken)
    98  
    99  	client := &http.Client{}
   100  	resp, err := client.Do(req)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	defer resp.Body.Close()
   106  
   107  	if resp.StatusCode != http.StatusOK {
   108  		respBody, err := io.ReadAll(resp.Body)
   109  		if err != nil {
   110  			return nil, fmt.Errorf("failed to read user profile response body. %s", err.Error())
   111  		}
   112  		return nil, fmt.Errorf("failed to get user profile. response body: %s", string(respBody))
   113  	}
   114  
   115  	body, err := io.ReadAll(resp.Body)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	var userProfileResponse ProvisioningUserProfileResponse
   121  	err = json.Unmarshal(body, &userProfileResponse)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	return &userProfileResponse, nil
   127  }
   128  
   129  // grabs BSL Roles and assigns them to a profile
   130  // accessToken (string): 		an Okta access token
   131  // nepOrg (string)				nep-organization value
   132  // returns roles, error
   133  func GetRoles(nepOrg string, envURL string, accessToken string) (string, error) {
   134  	url := envURL
   135  	req, err := http.NewRequest("GET", url, nil)
   136  	if err != nil {
   137  		return "", err
   138  	}
   139  
   140  	// set required headers
   141  	req.Header.Set("Authorization", "AccessToken "+accessToken)
   142  	req.Header.Set("nep-organization", nepOrg)
   143  
   144  	client := &http.Client{}
   145  	resp, err := client.Do(req)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  
   150  	defer resp.Body.Close()
   151  
   152  	// if not 200/201 to get BSL /introspect, return error
   153  	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
   154  		respBody, err := io.ReadAll(resp.Body)
   155  		if err != nil {
   156  			return "", fmt.Errorf("Unexpected status code: %d. Error reading response body: %s", resp.StatusCode, err.Error())
   157  		}
   158  		return "", fmt.Errorf("Unexpected status code: %d. Response body: %s", resp.StatusCode, string(respBody))
   159  	}
   160  
   161  	body, err := io.ReadAll(resp.Body)
   162  	if err != nil {
   163  		return "", err
   164  	}
   165  
   166  	var data map[string]interface{}
   167  	err = json.Unmarshal(body, &data)
   168  	if err != nil {
   169  		return "", err
   170  	}
   171  
   172  	// get roles as []interface{}
   173  	grantedRoles, ok := data["grantedRoles"].([]interface{})
   174  	if !ok {
   175  		return "", err
   176  	}
   177  
   178  	// convert []interfaces{} to an array of strings
   179  	output := []string{}
   180  	for _, item := range grantedRoles {
   181  		roleName, ok := item.(map[string]interface{})["roleName"].(string)
   182  		if ok {
   183  			output = append(output, roleName)
   184  		}
   185  	}
   186  
   187  	// serialize to make one big string
   188  	roles, err := util.Serialize(output)
   189  	if err != nil {
   190  		return "", err
   191  	}
   192  
   193  	return roles, nil
   194  }
   195  

View as plain text