// get_item.go is a source file designed to retrieve an m365 object from an // existing M365 account. Data displayed is representative of the current // serialization abstraction versioning used by Microsoft Graph and stored by Corso. package onedrive import ( "context" "encoding/json" "io" "net/http" "os" "github.com/alcionai/clues" "github.com/microsoft/kiota-abstractions-go/serialization" kjson "github.com/microsoft/kiota-serialization-json-go" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/spf13/cobra" . "github.com/alcionai/corso/src/cli/print" "github.com/alcionai/corso/src/cli/utils" "github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/str" "github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/credentials" "github.com/alcionai/corso/src/pkg/services/m365/api" ) const downloadURLKey = "@microsoft.graph.downloadUrl" // Required inputs from user for command execution var ( user, tenant, m365ID string ) func AddCommands(parent *cobra.Command) { exCmd := &cobra.Command{ Use: "onedrive", Short: "Get an M365ID item", RunE: handleOneDriveCmd, } fs := exCmd.PersistentFlags() fs.StringVar(&m365ID, "id", "", "m365 identifier for object") fs.StringVar(&user, "user", "", "m365 user id of M365 user") fs.StringVar(&tenant, "tenant", "", "m365 identifier for the tenant") cobra.CheckErr(exCmd.MarkPersistentFlagRequired("user")) cobra.CheckErr(exCmd.MarkPersistentFlagRequired("id")) parent.AddCommand(exCmd) } func handleOneDriveCmd(cmd *cobra.Command, args []string) error { if utils.HasNoFlagsAndShownHelp(cmd) { return nil } tid := str.First(tenant, os.Getenv(account.AzureTenantID)) ctx := clues.Add( cmd.Context(), "item_id", m365ID, "resource_owner", user, "tenant", tid) // get account info creds := account.M365Config{ M365: credentials.GetM365(), AzureTenantID: tid, } gr := graph.NewNoTimeoutHTTPWrapper() ac, err := api.NewClient(creds) if err != nil { return Only(ctx, clues.Wrap(err, "getting api client")) } err = runDisplayM365JSON(ctx, ac, gr, creds, user, m365ID) if err != nil { cmd.SilenceUsage = true cmd.SilenceErrors = true return Only(ctx, clues.Wrap(err, "getting item")) } return nil } type itemData struct { Size int `json:"size"` } type itemPrintable struct { Info json.RawMessage `json:"info"` Permissions json.RawMessage `json:"permissions"` Data itemData `json:"data"` } func (i itemPrintable) MinimumPrintable() any { return i } func runDisplayM365JSON( ctx context.Context, ac api.Client, gr graph.Requester, creds account.M365Config, userID, itemID string, ) error { drive, err := ac.Users().GetDefaultDrive(ctx, userID) if err != nil { return err } driveID := ptr.Val(drive.GetId()) it := itemPrintable{} item, err := ac.Drives().GetItem(ctx, driveID, itemID) if err != nil { return err } if item != nil { content, err := getDriveItemContent(ctx, gr, item) if err != nil { return err } // We could get size from item.GetSize(), but the // getDriveItemContent call is to ensure that we are able to // download the file. it.Data.Size = len(content) } sInfo, err := serializeObject(item) if err != nil { return err } err = json.Unmarshal([]byte(sInfo), &it.Info) if err != nil { return err } perms, err := ac.Drives().GetItemPermission(ctx, driveID, itemID) if err != nil { return err } sPerms, err := serializeObject(perms) if err != nil { return err } err = json.Unmarshal([]byte(sPerms), &it.Permissions) if err != nil { return err } PrettyJSON(ctx, it) return nil } func serializeObject(data serialization.Parsable) (string, error) { sw := kjson.NewJsonSerializationWriter() err := sw.WriteObjectValue("", data) if err != nil { return "", clues.Wrap(err, "writing serializing info") } content, err := sw.GetSerializedContent() if err != nil { return "", clues.Wrap(err, "getting serializing info") } return string(content), err } func getDriveItemContent( ctx context.Context, gr graph.Requester, item models.DriveItemable, ) ([]byte, error) { url, ok := item.GetAdditionalData()[downloadURLKey].(*string) if !ok { return nil, clues.New("retrieving download url") } resp, err := gr.Request(ctx, http.MethodGet, *url, nil, nil) if err != nil { return nil, clues.New("downloading item").With("error", err) } defer resp.Body.Close() content, err := io.ReadAll(resp.Body) if err != nil { return nil, clues.New("read downloaded item").With("error", err) } return content, nil }