Skip to content

Commit

Permalink
(fix) using archived and active users for sync
Browse files Browse the repository at this point in the history
closes #1
  • Loading branch information
katallaxie committed Jul 22, 2020
1 parent f95078e commit 317d397
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 136 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,4 @@
## 1.0.0-rc.1

- #1 Fix: Pagination does not work
- #3 Refactor: New features for Serverless Repo and Google best practices
6 changes: 5 additions & 1 deletion cmd/root.go
Expand Up @@ -15,6 +15,7 @@
package cmd

import (
"context"
"fmt"
"os"

Expand Down Expand Up @@ -47,7 +48,10 @@ var rootCmd = &cobra.Command{
Apps (G-Suite) users to AWS Single Sign-on (AWS SSO)
Complete documentation is available at https://github.com/awslabs/ssosync`,
RunE: func(cmd *cobra.Command, args []string) error {
err := internal.DoSync(cfg)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

err := internal.DoSync(ctx, cfg)
if err != nil {
return err
}
Expand Down
15 changes: 6 additions & 9 deletions internal/aws/client.go
Expand Up @@ -42,7 +42,6 @@ const (
// IClient represents an interface of methods used
// to communicate with AWS SSO
type IClient interface {
GetUsers() (*map[string]User, error)
GetGroups() (*map[string]Group, error)
IsUserInGroup(*User, *Group) (bool, error)
FindUserByEmail(string) (*User, error)
Expand Down Expand Up @@ -364,10 +363,10 @@ func (c *Client) RemoveUserFromGroup(u *User, g *Group) error {
}

// FindUserByEmail will find the user by the email address specified
func (c *Client) FindUserByEmail(email string) (user *User, err error) {
func (c *Client) FindUserByEmail(email string) (*User, error) {
startURL, err := url.Parse(c.endpointURL.String())
if err != nil {
return
return nil, err
}

filter := fmt.Sprintf("userName eq \"%s\"", email)
Expand All @@ -380,23 +379,21 @@ func (c *Client) FindUserByEmail(email string) (user *User, err error) {

resp, err := c.sendRequest(http.MethodGet, startURL.String())
if err != nil {
return
return nil, err
}

var r UserFilterResults
err = json.Unmarshal(resp, &r)
if err != nil {
return
return nil, err
}

if r.TotalResults != 1 {
err = fmt.Errorf("%s not found in AWS SSO", email)
return
return nil, err
}

user = &r.Resources[0]

return
return &r.Resources[0], nil
}

// CreateUser will create the user specified
Expand Down
79 changes: 0 additions & 79 deletions internal/aws/client_test.go
Expand Up @@ -82,85 +82,6 @@ func TestNewClient(t *testing.T) {
assert.Nil(t, c)
}

func TestClient_GetUsers(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

x := mock.NewMockIHttpClient(ctrl)

c, err := NewClient(x, &Config{
Endpoint: "https://scim.example.com/",
Token: "bearerToken",
})
assert.NoError(t, err)

r := `
{
"totalResults": 1,
"itemsPerPage": 1,
"startIndex": 1,
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
],
"Resources": [
{
"id": "93671c1e63-33bc8a92-2fb0-487b-93ef-7a618aef932a",
"meta": {
"resourceType": "User",
"created": "2020-04-16T14:22:56Z",
"lastModified": "2020-04-16T14:22:56Z"
},
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"userName": "lpackham@foo.org.uk",
"name": {
"familyName": "Packham",
"givenName": "Lee"
},
"displayName": "Lee Packham",
"active": true,
"emails": [
{
"value": "lpackham@foo.org.uk",
"type": "work",
"primary": true
}
],
"addresses": [
{
"type": "work"
}
]
}
]
}
`

calledURL, _ := url.Parse("https://scim.example.com/Users?count=10&startIndex=1")

req := httpReqMatcher{httpReq: &http.Request{
URL: calledURL,
Method: http.MethodGet,
}}

// We only have enough users for one page, so we should only
// see one call.
x.EXPECT().Do(&req).MaxTimes(1).Return(&http.Response{
Status: "OK",
StatusCode: 200,
Body: nopCloser{bytes.NewBufferString(r)},
}, nil)

users, err := c.GetUsers()

assert.NoError(t, err)

// Check there's only user
assert.Equal(t, len(*users), 1)
assert.Contains(t, *users, "lpackham@foo.org.uk")
}

func TestClient_GetGroups(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down
34 changes: 22 additions & 12 deletions internal/google/client.go
Expand Up @@ -25,6 +25,7 @@ import (
// Client is the Interface for the Client
type Client interface {
GetUsers() ([]*admin.User, error)
GetDeletedUsers() ([]*admin.User, error)
GetGroups() ([]*admin.Group, error)
GetGroupMembers(*admin.Group) ([]*admin.Member, error)
}
Expand All @@ -35,9 +36,7 @@ type client struct {
}

// NewClient creates a new client for Google's Admin API
func NewClient(adminEmail string, serviceAccountKey []byte) (Client, error) {
ctx := context.Background()

func NewClient(ctx context.Context, adminEmail string, serviceAccountKey []byte) (Client, error) {
config, err := google.JWTConfigFromJSON(serviceAccountKey, admin.AdminDirectoryGroupReadonlyScope,
admin.AdminDirectoryGroupMemberReadonlyScope,
admin.AdminDirectoryUserReadonlyScope)
Expand All @@ -61,10 +60,21 @@ func NewClient(adminEmail string, serviceAccountKey []byte) (Client, error) {
}, nil
}

// GetDeletedUsers will get the deleted users from the Google's Admin API.
func (c *client) GetDeletedUsers() ([]*admin.User, error) {
u := make([]*admin.User, 0)
err := c.service.Users.List().Customer("my_customer").ShowDeleted("true").Pages(c.ctx, func(users *admin.Users) error {
u = append(u, users.Users...)
return nil
})

return u, err
}

// GetUsers will get the users from Google's Admin API
func (c *client) GetUsers() (u []*admin.User, err error) {
u = make([]*admin.User, 0)
err = c.service.Users.List().Customer("my_customer").Pages(c.ctx, func(users *admin.Users) error {
func (c *client) GetUsers() ([]*admin.User, error) {
u := make([]*admin.User, 0)
err := c.service.Users.List().Customer("my_customer").Pages(c.ctx, func(users *admin.Users) error {
u = append(u, users.Users...)
return nil
})
Expand All @@ -73,9 +83,9 @@ func (c *client) GetUsers() (u []*admin.User, err error) {
}

// GetGroups will get the groups from Google's Admin API
func (c *client) GetGroups() (g []*admin.Group, err error) {
g = make([]*admin.Group, 0)
err = c.service.Groups.List().Customer("my_customer").Pages(context.TODO(), func(groups *admin.Groups) error {
func (c *client) GetGroups() ([]*admin.Group, error) {
g := make([]*admin.Group, 0)
err := c.service.Groups.List().Customer("my_customer").Pages(context.TODO(), func(groups *admin.Groups) error {
g = append(g, groups.Groups...)
return nil
})
Expand All @@ -84,9 +94,9 @@ func (c *client) GetGroups() (g []*admin.Group, err error) {
}

// GetGroupMembers will get the members of the group specified
func (c *client) GetGroupMembers(g *admin.Group) (m []*admin.Member, err error) {
m = make([]*admin.Member, 0)
err = c.service.Members.List(g.Id).Pages(context.TODO(), func(members *admin.Members) error {
func (c *client) GetGroupMembers(g *admin.Group) ([]*admin.Member, error) {
m := make([]*admin.Member, 0)
err := c.service.Members.List(g.Id).Pages(context.TODO(), func(members *admin.Members) error {
m = append(m, members.Members...)
return nil
})
Expand Down
69 changes: 34 additions & 35 deletions internal/sync.go
Expand Up @@ -15,6 +15,7 @@
package internal

import (
"context"
"io/ioutil"
"net/http"

Expand Down Expand Up @@ -52,54 +53,52 @@ func New(a aws.IClient, g google.Client) ISyncGSuite {

// SyncUsers will Sync Google Users to AWS SSO SCIM
func (s *SyncGSuite) SyncUsers() error {
log.Info("Start user sync")
log.Info("Get AWS Users")

awsUsers, err := s.aws.GetUsers()
log.Debug("get deleted users")
deletedUsers, err := s.google.GetDeletedUsers()
if err != nil {
return err
}

log.Debug("Get Google Users")
for _, u := range deletedUsers {
uu, _ := s.aws.FindUserByEmail(u.PrimaryEmail)
if uu == nil {
continue
}

log.WithFields(log.Fields{
"email": u.PrimaryEmail,
}).Info("deleting google user")

if err := s.aws.DeleteUser(uu); err != nil {
return err
}
}

log.Debug("get active google users")
googleUsers, err := s.google.GetUsers()
if err != nil {
return err
}

for _, u := range googleUsers {
log := log.WithFields(log.Fields{
ll := log.WithFields(log.Fields{
"email": u.PrimaryEmail,
})

log.Debug("Check user")

if awsUser, ok := (*awsUsers)[u.PrimaryEmail]; ok {
log.Debug("Found user")
s.users[awsUser.Username] = &awsUser
} else {
log.Info("Create user in AWS")
newUser, err := s.aws.CreateUser(aws.NewUser(
u.Name.GivenName,
u.Name.FamilyName,
u.PrimaryEmail,
))
if err != nil {
return err
}

s.users[newUser.Username] = newUser
ll.Debug("finding user")
uu, _ := s.aws.FindUserByEmail(u.PrimaryEmail)
if uu != nil {
continue
}
}

log.Info("Clean up AWS Users")
for _, u := range *awsUsers {
if _, ok := s.users[u.Username]; !ok {
log.WithField("email", u.Username).Info("Delete User in AWS")

err := s.aws.DeleteUser(&u)
if err != nil {
return err
}
ll.Info("creating user")
_, err := s.aws.CreateUser(aws.NewUser(
u.Name.GivenName,
u.Name.FamilyName,
u.PrimaryEmail,
))
if err != nil {
return err
}
}

Expand Down Expand Up @@ -207,7 +206,7 @@ func (s *SyncGSuite) SyncGroups() error {

// DoSync will create a logger and run the sync with the paths
// given to do the sync.
func DoSync(cfg *config.Config) error {
func DoSync(ctx context.Context, cfg *config.Config) error {
log.Info("Creating the Google and AWS Clients needed")

creds := []byte(cfg.GoogleCredentials)
Expand All @@ -220,7 +219,7 @@ func DoSync(cfg *config.Config) error {
creds = b
}

googleClient, err := google.NewClient(cfg.GoogleAdmin, creds)
googleClient, err := google.NewClient(ctx, cfg.GoogleAdmin, creds)
if err != nil {
return err
}
Expand Down

0 comments on commit 317d397

Please sign in to comment.