Skip to content

Commit

Permalink
Merge pull request #30 from bensallen/admin-mutations-3
Browse files Browse the repository at this point in the history
Add functions for Create/Modify/Delete User, Associate/Disassociate Group, and User to url.Values
  • Loading branch information
mbish authored Oct 27, 2021
2 parents 2e18639 + 5723b6d commit 72da735
Show file tree
Hide file tree
Showing 4 changed files with 438 additions and 12 deletions.
145 changes: 134 additions & 11 deletions admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/url"
"reflect"
"strconv"

duoapi "github.com/duosecurity/duo_api_golang"
Expand Down Expand Up @@ -36,24 +37,53 @@ func New(base duoapi.DuoApi) *Client {

// User models a single user.
type User struct {
Alias1 *string
Alias2 *string
Alias3 *string
Alias4 *string
Alias1 *string `url:"alias1"`
Alias2 *string `url:"alias2"`
Alias3 *string `url:"alias3"`
Alias4 *string `url:"alias4"`
Created uint64
Email string
FirstName *string
Email string `url:"email"`
FirstName *string `url:"firstname"`
Groups []Group
LastDirectorySync *uint64 `json:"last_directory_sync"`
LastLogin *uint64 `json:"last_login"`
LastName *string
Notes string
LastName *string `url:"lastname"`
Notes string `url:"notes"`
Phones []Phone
RealName *string
Status string
RealName *string `url:"realname"`
Status string `url:"status"`
Tokens []Token
UserID string `json:"user_id"`
Username string
Username string `url:"username"`
}

// URLValues transforms a User into url.Values using the 'url' struct tag to
// define the key of the map. Fields are skiped if the value is empty.
func (u *User) URLValues() url.Values {
params := url.Values{}

t := reflect.TypeOf(u).Elem()
v := reflect.ValueOf(u).Elem()

// Iterate over all available struct fields
for i := 0; i < t.NumField(); i++ {
tag := t.Field(i).Tag.Get("url")
if tag == "" {
continue
}
// Skip fields have a zero value.
if v.Field(i).Interface() == reflect.Zero(v.Field(i).Type()).Interface() {
continue
}
var val string
if t.Field(i).Type.Kind() == reflect.Ptr {
val = fmt.Sprintf("%v", v.Field(i).Elem())
} else {
val = fmt.Sprintf("%v", v.Field(i))
}
params[tag] = []string{val}
}
return params
}

// Group models a group to which users may belong.
Expand Down Expand Up @@ -243,6 +273,60 @@ func (c *Client) GetUser(userID string) (*GetUserResult, error) {
return result, nil
}

// CreateUser calls POST /admin/v1/users
// See https://duo.com/docs/adminapi#create-user
func (c *Client) CreateUser(params url.Values) (*GetUserResult, error) {
path := "/admin/v1/users"

_, body, err := c.SignedCall(http.MethodPost, path, params, duoapi.UseTimeout)
if err != nil {
return nil, err
}

result := &GetUserResult{}
err = json.Unmarshal(body, result)
if err != nil {
return nil, err
}
return result, nil
}

// ModifyUser calls POST /admin/v1/users/:user_id
// See https://duo.com/docs/adminapi#modify-user
func (c *Client) ModifyUser(userID string, params url.Values) (*GetUserResult, error) {
path := fmt.Sprintf("/admin/v1/users/%s", userID)

_, body, err := c.SignedCall(http.MethodPost, path, params, duoapi.UseTimeout)
if err != nil {
return nil, err
}

result := &GetUserResult{}
err = json.Unmarshal(body, result)
if err != nil {
return nil, err
}
return result, nil
}

// DeleteUser calls DELETE /admin/v1/users/:user_id
// See https://duo.com/docs/adminapi#delete-user
func (c *Client) DeleteUser(userID string) (*duoapi.StatResult, error) {
path := fmt.Sprintf("/admin/v1/users/%s", userID)

_, body, err := c.SignedCall(http.MethodDelete, path, nil, duoapi.UseTimeout)
if err != nil {
return nil, err
}

result := &duoapi.StatResult{}
err = json.Unmarshal(body, result)
if err != nil {
return nil, err
}
return result, nil
}

// GetUserGroups calls GET /admin/v1/users/:user_id/groups
// See https://duo.com/docs/adminapi#retrieve-groups-by-user-id
func (c *Client) GetUserGroups(userID string, options ...func(*url.Values)) (*GetGroupsResult, error) {
Expand All @@ -262,6 +346,45 @@ func (c *Client) GetUserGroups(userID string, options ...func(*url.Values)) (*Ge
return response.(*GetGroupsResult), nil
}

// AssociateGroupWithUser calls POST /admin/v1/users/:user_id/groups
// See https://duo.com/docs/adminapi#associate-group-with-user
func (c *Client) AssociateGroupWithUser(userID string, groupID string) (*duoapi.StatResult, error) {
path := fmt.Sprintf("/admin/v1/users/%s/groups", userID)

params := url.Values{}
params.Set("group_id", groupID)

_, body, err := c.SignedCall(http.MethodPost, path, params, duoapi.UseTimeout)
if err != nil {
return nil, err
}

result := &duoapi.StatResult{}
err = json.Unmarshal(body, result)
if err != nil {
return nil, err
}
return result, nil
}

// DisassociateGroupFromUser calls POST /admin/v1/users/:user_id/groups
// See https://duo.com/docs/adminapi#disassociate-group-from-user
func (c *Client) DisassociateGroupFromUser(userID string, groupID string) (*duoapi.StatResult, error) {
path := fmt.Sprintf("/admin/v1/users/%s/groups/%s", userID, groupID)

_, body, err := c.SignedCall(http.MethodDelete, path, nil, duoapi.UseTimeout)
if err != nil {
return nil, err
}

result := &duoapi.StatResult{}
err = json.Unmarshal(body, result)
if err != nil {
return nil, err
}
return result, nil
}

func (c *Client) retrieveUserGroups(userID string, params url.Values) (*GetGroupsResult, error) {
path := fmt.Sprintf("/admin/v1/users/%s/groups", userID)

Expand Down
Loading

0 comments on commit 72da735

Please sign in to comment.