Drivers whose source API surfaces an account-status signal return every account (including inactive / suspended / deleted) and populate Active accordingly; drivers without such a signal no longer fabricate it. The fetch pipeline records every account with decision PENDING and no flags. Producing flags or a non-PENDING decision is reserved to human reviewers or a purpose-built agent run against the campaign -- the engine must not form an implicit verdict. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
109 lines
3.2 KiB
Go
109 lines
3.2 KiB
Go
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
|
//
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
// copyright notice and this permission notice appear in all copies.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
// PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
package drivers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"go.probo.inc/probo/pkg/coredata"
|
|
)
|
|
|
|
// CSVDriver supports both identity and access use cases from uploaded CSV
|
|
// files. No external connector is needed.
|
|
//
|
|
// Expected CSV columns (header required): email, full_name, role, job_title,
|
|
// is_admin, active, external_id
|
|
type CSVDriver struct {
|
|
reader io.Reader
|
|
}
|
|
|
|
func NewCSVDriver(reader io.Reader) *CSVDriver {
|
|
return &CSVDriver{reader: reader}
|
|
}
|
|
|
|
func (d *CSVDriver) ListAccounts(_ context.Context) ([]AccountRecord, error) {
|
|
r := csv.NewReader(d.reader)
|
|
r.FieldsPerRecord = -1
|
|
|
|
// Read header
|
|
header, err := r.Read()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read CSV header: %w", err)
|
|
}
|
|
|
|
colIndex := make(map[string]int)
|
|
for i, col := range header {
|
|
colIndex[strings.TrimSpace(strings.ToLower(col))] = i
|
|
}
|
|
if _, ok := colIndex["email"]; !ok {
|
|
return nil, fmt.Errorf("cannot parse CSV: missing required column email")
|
|
}
|
|
|
|
var records []AccountRecord
|
|
|
|
for {
|
|
row, err := r.Read()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read CSV row: %w", err)
|
|
}
|
|
|
|
record := AccountRecord{
|
|
MFAStatus: coredata.MFAStatusUnknown,
|
|
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
|
|
AccountType: coredata.AccessEntryAccountTypeUser,
|
|
}
|
|
|
|
if idx, ok := colIndex["email"]; ok && idx < len(row) {
|
|
record.Email = strings.TrimSpace(row[idx])
|
|
}
|
|
if idx, ok := colIndex["full_name"]; ok && idx < len(row) {
|
|
record.FullName = strings.TrimSpace(row[idx])
|
|
}
|
|
if idx, ok := colIndex["role"]; ok && idx < len(row) {
|
|
record.Role = strings.TrimSpace(row[idx])
|
|
}
|
|
if idx, ok := colIndex["job_title"]; ok && idx < len(row) {
|
|
record.JobTitle = strings.TrimSpace(row[idx])
|
|
}
|
|
if idx, ok := colIndex["is_admin"]; ok && idx < len(row) {
|
|
record.IsAdmin = strings.TrimSpace(strings.ToLower(row[idx])) == "true"
|
|
}
|
|
if idx, ok := colIndex["active"]; ok && idx < len(row) {
|
|
record.Active = new(strings.TrimSpace(strings.ToLower(row[idx])) == "true")
|
|
}
|
|
if idx, ok := colIndex["external_id"]; ok && idx < len(row) {
|
|
record.ExternalID = strings.TrimSpace(row[idx])
|
|
}
|
|
if idx, ok := colIndex["account_type"]; ok && idx < len(row) {
|
|
if strings.TrimSpace(strings.ToUpper(row[idx])) == "SERVICE_ACCOUNT" {
|
|
record.AccountType = coredata.AccessEntryAccountTypeServiceAccount
|
|
}
|
|
}
|
|
|
|
if record.Email != "" {
|
|
records = append(records, record)
|
|
}
|
|
}
|
|
|
|
return records, nil
|
|
}
|