Skip to content
This repository has been archived by the owner on Mar 24, 2021. It is now read-only.

Add command to display list of inactive users #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 3 additions & 1 deletion cernboxcop.spec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Name: cernboxcop
Summary: CERNBox cop helps the ops team to be more efficient
Version: 1.0.11
Version: 1.0.12
Release: 1%{?dist}
License: AGPLv3
BuildRoot: %{_tmppath}/%{name}-buildroot
Expand Down Expand Up @@ -51,6 +51,8 @@ rm -rf %buildroot/


%changelog
* Wed Nov 25 2020 Ishank Arora <[email protected]> 1.0.12
- Add command to display list of inactive users
* Tue Nov 25 2020 Hugo Gonzalez Labrador <[email protected]> 1.0.11
- Add OTG commands
* Fri Nov 20 2020 Hugo Gonzalez Labrador <[email protected]> 1.0.10
Expand Down
17 changes: 9 additions & 8 deletions cmd/accounting.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/cs3org/reva/pkg/eosclient"
"github.com/dustin/go-humanize"
"github.com/leekchan/accounting"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tj/go-spin"
"gopkg.in/ldap.v3"
"io/ioutil"
"net/http"
"os"
Expand All @@ -23,6 +15,15 @@ import (
"sync"
"sync/atomic"
"time"

"github.com/cs3org/reva/pkg/eosclient"
"github.com/dustin/go-humanize"
"github.com/leekchan/accounting"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tj/go-spin"
"gopkg.in/ldap.v3"
)

const FE = "CERNBox"
Expand Down
37 changes: 37 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"bufio"
"context"
"database/sql"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"strings"
Expand Down Expand Up @@ -120,6 +124,39 @@ func getLDAP() *ldap.Conn {
return l
}

func getGrappaURLAndToken() (string, string, error) {
params := url.Values{
"grant_type": {"client_credentials"},
"audience": {viper.GetString("grappa_target_api")},
}

client := &http.Client{}
req, err := http.NewRequest("POST", viper.GetString("oidc_token_endpoint"), strings.NewReader(params.Encode()))
if err != nil {
return "", "", err
}
req.SetBasicAuth(viper.GetString("grappa_client_id"), viper.GetString("grappa_client_secret"))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")

res, err := client.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}

var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return "", "", err
}
return viper.GetString("grappa_api_url"), result["access_token"].(string), nil
}

func getEOS(mgm string) *eosclient.Client {
eosClientOpts := &eosclient.Options{
URL: mgm,
Expand Down
199 changes: 189 additions & 10 deletions cmd/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@ package cmd

import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"

"github.com/go-redis/redis"
"github.com/spf13/cobra"
"github.com/tj/go-spin"
"gopkg.in/ldap.v3"
)

const timeFormat = "2006-01-02T15:04:05.999999"

func init() {
rootCmd.AddCommand(userCmd)
userCmd.AddCommand(userCheckCmd)
userCmd.AddCommand(userGroupsCmd)
userCmd.AddCommand(userListInactiveCmd)
}

var userCmd = &cobra.Command{
Expand Down Expand Up @@ -44,16 +53,6 @@ var userCheckCmd = &cobra.Command{
},
}

var prettyUser = func(userInfos ...*userInfo) {
cols := []string{"Account", "Type", "Name", "Department", "Group", "Section", "Mail", "Phone"}
rows := make([][]string, 0, len(userInfos))
for _, ui := range userInfos {
row := []string{ui.Account, ui.AccountType, ui.Name, ui.Department, ui.Group, ui.Section, ui.Mail, ui.Phone}
rows = append(rows, row)
}
pretty(cols, rows)
}

var userGroupsCmd = &cobra.Command{
Use: "groups <username>",
Short: "Retrieves the groups the the user is member of",
Expand All @@ -71,6 +70,54 @@ var userGroupsCmd = &cobra.Command{
},
}

var userListInactiveCmd = &cobra.Command{
Use: "list-inactive <days>",
Short: "Lists the user accounts which have been inactive for more than a given number of days",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
exit(cmd)
}

days, err := strconv.Atoi(strings.TrimSpace(args[0]))
if err != nil {
log.Err(err).Msgf("value for days is not an integer: %s", args[0])
}

apiEP, token, err := getGrappaURLAndToken()
if err != nil {
log.Err(err).Msg("error getting token")
}

info, err := getInactiveUsers(apiEP, token, days)
if err != nil {
log.Err(err).Msg("error getting token")
}

prettyInactiveUsers(info...)

},
}

func prettyUser(userInfos ...*userInfo) {
cols := []string{"Account", "Type", "Name", "Department", "Group", "Section", "Mail", "Phone"}
rows := make([][]string, 0, len(userInfos))
for _, ui := range userInfos {
row := []string{ui.Account, ui.AccountType, ui.Name, ui.Department, ui.Group, ui.Section, ui.Mail, ui.Phone}
rows = append(rows, row)
}
pretty(cols, rows)
}

func prettyInactiveUsers(userInfos ...*userInfo) {
cols := []string{"Account", "Name", "Mail", "Blocking time"}
rows := make([][]string, 0, len(userInfos))
for _, ui := range userInfos {
row := []string{ui.Account, ui.Name, ui.Mail, ui.BlockingTime.String()}
rows = append(rows, row)
}
pretty(cols, rows)
}

func getHomePath(username string) string {
letter := string(username[0])
return fmt.Sprintf("/eos/user/%s/%s", letter, username)
Expand Down Expand Up @@ -263,6 +310,137 @@ func getUserGroups(uid string) []string {
return gids
}

func getInactiveUsers(apiEP, token string, days int) ([]*userInfo, error) {
url := "/api/v1.0/Identity?filter=activeUser:false&field=upn&field=primaryAccountEmail&field=displayName&field=type&field=uid&field=gid&field=primaryAccountId"
users := []*userInfo{}
i := 0
for {
u, nextPage, err := getInactiveUsersByPage(apiEP, url, token, days, i+1)
if err != nil {
return nil, err
}
i += 1
users = append(users, u...)
url = nextPage
if nextPage == "" {
break
}
}
return users, nil
}

func getInactiveUsersByPage(apiEP, url, token string, days, pageIndex int) ([]*userInfo, string, error) {
deadline := time.Now().AddDate(0, 0, -1*days)

url = apiEP + url
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, "", err
}
req.Header.Set("Authorization", "Bearer "+token)

res, err := client.Do(req)
if err != nil {
return nil, "", err
}
defer res.Body.Close()

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, "", err
}

var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return nil, "", err
}
userData, ok := result["data"].([]interface{})
if !ok {
return nil, "", errors.New("rest: error in type assertion")
}

s := spin.New()

users := []*userInfo{}
for i, u := range userData {
fmt.Fprintf(os.Stderr, "\r %s Processing inactive users from page %d: %d/%d", s.Next(), pageIndex, i+1, len(userData))
info, ok := u.(map[string]interface{})
if !ok {
return nil, "", errors.New("rest: error in type assertion")
}

// Check blocking time of the primary account of the user
accountsURL := fmt.Sprintf("%s/api/v1.0/Account?filter=id:%s", apiEP, info["primaryAccountId"].(string))
req, err = http.NewRequest("GET", accountsURL, nil)
if err != nil {
return nil, "", err
}
req.Header.Set("Authorization", "Bearer "+token)

res, err = client.Do(req)
if err != nil {
return nil, "", err
}
defer res.Body.Close()

body, err = ioutil.ReadAll(res.Body)
if err != nil {
return nil, "", err
}

var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return nil, "", err
}
accountData, ok := result["data"].([]interface{})
if !ok {
return nil, "", errors.New("rest: error in type assertion")
}
accountInfo, ok := accountData[0].(map[string]interface{})
if !ok {
return nil, "", errors.New("rest: error in type assertion")
}

blockingTime, ok := accountInfo["blockingTime"].(string)
if !ok {
continue
}

t, err := time.Parse(timeFormat, blockingTime)
if err != nil {
return nil, "", err
}
if t.After(deadline) {
users = append(users, &userInfo{
Account: info["upn"].(string),
Name: info["displayName"].(string),
Mail: info["primaryAccountEmail"].(string),
UID: fmt.Sprintf("%0.f", info["uid"]),
GID: fmt.Sprintf("%0.f", info["gid"]),
BlockingTime: t,
})
}
}

var nextPage string
if pagination, ok := result["pagination"].(map[string]interface{}); ok {
if links, ok := pagination["links"].(map[string]interface{}); ok {
nextPage, _ = links["next"].(string)
}
}

return users, nextPage, nil

}

func filterUsersByDate(users []*userInfo, days int) ([]*userInfo, error) {
// TODO: Add filters once bug in grappa is fixed
return users, nil
}

func newUserInfo() *userInfo {
return &userInfo{
AccountOwner: &userInfo{},
Expand All @@ -281,6 +459,7 @@ type userInfo struct {
AccountOwner *userInfo
AccountOwnerDN string
Phone string
BlockingTime time.Time
}

func (ui *userInfo) accountTypeHuman() string {
Expand Down