Skip to content

Instantly share code, notes, and snippets.

@Zenithar
Created November 29, 2019 07:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zenithar/9f0a36a8ad2bb1221246b6b28aa04da7 to your computer and use it in GitHub Desktop.
Save Zenithar/9f0a36a8ad2bb1221246b6b28aa04da7 to your computer and use it in GitHub Desktop.
Okta user list CSV exporter
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package main
import (
"context"
"encoding/csv"
"flag"
"fmt"
"log"
"net/url"
"os"
"time"
"golang.org/x/sync/errgroup"
"github.com/okta/okta-sdk-golang/okta"
"github.com/okta/okta-sdk-golang/okta/query"
"github.com/peterhellberg/link"
)
var (
oktaDomain = flag.String("domain", "", "Okta domain to use for queries")
oktaAPIKey = flag.String("apikey", "", "Okta API Key")
)
func init() {
flag.Parse()
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Initialize Okta Client
client, err := okta.NewClient(ctx, okta.WithOrgUrl(*oktaDomain), okta.WithToken(*oktaAPIKey))
if err != nil {
log.Fatalln("error with okta client :", err)
}
results := make(chan *okta.User)
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
// Query user list
if err := collectUsers(client, &query.Params{Limit: 200, SortOrder: "0"}, results); err != nil {
return err
}
return nil
})
g.Go(func() error {
// Query user list
if err := collectUsers(client, &query.Params{Filter: `status eq "DEPROVISIONED"`, Limit: 200, SortOrder: "0"}, results); err != nil {
return err
}
return nil
})
go func() {
g.Wait()
close(results)
}()
// Initialize csv writer
w := csv.NewWriter(os.Stdout)
w.Write([]string{"USER_ID", "STATUS", "EMAIL", "CREATED_ON"})
for user := range results {
w.Write([]string{user.Id, user.Status, extractEmail(user), user.Created.UTC().Format(time.RFC3339)})
}
if err := w.Error(); err != nil {
log.Fatalln(err)
}
w.Flush()
if err := g.Wait(); err != nil {
log.Fatalln(err)
}
}
// -----------------------------------------------------------------------------
// Recursively list apps until no next links are returned
func collectUsers(client *okta.Client, qp *query.Params, results chan *okta.User) error {
users, res, err := client.User.ListUsers(qp)
if err != nil {
return err
}
for _, u := range users {
results <- u
}
if after := getAfterParam(res); after != "" {
qp.After = after
return collectUsers(client, qp, results)
}
return nil
}
// Grabs after link from link headers if it exists
func getAfterParam(res *okta.Response) string {
if res == nil {
return ""
}
linkList := link.ParseHeader(res.Header)
for _, l := range linkList {
if l.Rel == "next" {
parsedURL, err := url.Parse(l.URI)
if err != nil {
continue
}
q := parsedURL.Query()
return q.Get("after")
}
}
return ""
}
func extractEmail(user *okta.User) string {
if user.Profile != nil {
profile := *user.Profile
if value, ok := profile["login"]; ok {
return fmt.Sprintf("%v", value)
}
}
return ""
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment