package azure import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "os" "path" "time" ) type Graph struct { *accessToken tenantId string groupId string } func NewGraphByEnv() *Graph { tenantId := os.Getenv("GRAPH_TENANT_ID") groupId := os.Getenv("GRAPH_GROUP_ID") return NewGraph(tenantId, groupId) } func NewGraph(tenantId string, groupId string) *Graph { return &Graph{ accessToken: &accessToken{ url: fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId), values: url.Values{ "grant_type": {"client_credentials"}, "client_id": {microsoftAppId()}, "scope": {"https://graph.microsoft.com/.default"}, "client_secret": {microsoftAppPassword()}, }, }, tenantId: tenantId, groupId: groupId, } } func (s Graph) GroupId() string { return s.groupId } var errGroupIdMissing = errors.New("GRAPH_GROUP_ID is missing") func (s Graph) ReadFile(relativePath string) (io.ReadCloser, error) { if len(s.groupId) == 0 { return nil, errGroupIdMissing } token, err := s.getAuthoizationToken() if err != nil { return nil, err } requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root:/%s:/content", s.groupId, relativePath) req, _ := http.NewRequest("GET", requrl, nil) req.Header.Add("Authorization", token) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, errors.New(resp.Status) } return resp.Body, nil } func (s Graph) GetWebURLOfFile(relativePath string) (string, error) { if len(s.groupId) == 0 { return "", errGroupIdMissing } token, err := s.getAuthoizationToken() if err != nil { return "", err } requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root:/%s:", s.groupId, relativePath) req, _ := http.NewRequest("GET", requrl, nil) req.Header.Add("Authorization", token) resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer func() { io.Copy(io.Discard, resp.Body) resp.Body.Close() }() itemdoc, _ := io.ReadAll(resp.Body) var item map[string]interface{} json.Unmarshal(itemdoc, &item) url, ok := item["webUrl"] if ok { return url.(string), nil } return "", nil } func (s Graph) DownloadFile(relativePath string, outputFile string) error { body, err := s.ReadFile(relativePath) if err != nil { return err } defer body.Close() // Create the file out, err := os.Create(outputFile) if err != nil { return err } defer out.Close() // Write the body to file _, err = io.Copy(out, body) return err } func (s Graph) UploadStream(relativePath string, reader io.Reader) (string, error) { if len(s.groupId) == 0 { return "", errGroupIdMissing } token, err := s.getAuthoizationToken() if err != nil { return "", err } requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root:/%s:/content", s.groupId, relativePath) req, _ := http.NewRequest("PUT", requrl, reader) req.Header.Add("Authorization", token) req.Header.Add("Content-Type", "text/plain") resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer func() { io.Copy(io.Discard, resp.Body) resp.Body.Close() }() body, _ := io.ReadAll(resp.Body) unquot, _ := unescapeUnicodeCharactersInJSON(body) var drive map[string]interface{} err = json.Unmarshal([]byte(unquot), &drive) if err != nil { return "", err } if errnode, ok := drive["error"]; ok { errmsg := errnode.(map[string]interface{})["message"].(string) return "", errors.New(errmsg) } return drive["webUrl"].(string), nil } func (s Graph) UploadBytes(relativePath string, content []byte) (string, error) { return s.UploadStream(relativePath, bytes.NewReader(content)) } func (s Graph) GetChannelFilesFolderName(channel string) (string, error) { if len(s.groupId) == 0 { return "", errGroupIdMissing } token, err := s.getAuthoizationToken() if err != nil { return "", err } requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/teams/%s/channels/%s/filesFolder", s.groupId, channel) req, _ := http.NewRequest("GET", requrl, nil) req.Header.Add("Authorization", token) req.Header.Add("Accept", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer func() { io.Copy(io.Discard, resp.Body) resp.Body.Close() }() body, _ := io.ReadAll(resp.Body) unquot, _ := unescapeUnicodeCharactersInJSON(body) var drive map[string]interface{} err = json.Unmarshal([]byte(unquot), &drive) if err != nil { return "", err } if errnode, ok := drive["error"]; ok { errmsg := errnode.(map[string]interface{})["message"].(string) return "", errors.New(errmsg) } return drive["name"].(string), nil } func (s Graph) listChildren(path string) (map[string]interface{}, error) { if len(s.groupId) == 0 { return nil, errGroupIdMissing } token, err := s.getAuthoizationToken() if err != nil { return nil, err } // groupId := "612620b7-cd90-4b3f-a9bd-d34cddf52517" var requrl string if len(path) == 0 { requrl = fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root/children", s.groupId) } else { requrl = fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root:/%s:/children", s.groupId, path) } req, _ := http.NewRequest("GET", requrl, nil) req.Header.Add("Authorization", token) req.Header.Add("Accept", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer func() { io.Copy(io.Discard, resp.Body) resp.Body.Close() }() body, _ := io.ReadAll(resp.Body) unquot, _ := unescapeUnicodeCharactersInJSON(body) var drive map[string]interface{} err = json.Unmarshal([]byte(unquot), &drive) if err != nil { return nil, err } if errnode, ok := drive["error"]; ok { errmsg := errnode.(map[string]interface{})["message"].(string) return nil, errors.New(errmsg) } return drive, nil } func (s Graph) ListFolders(parent string) ([]string, error) { drive, err := s.listChildren(parent) if err != nil { return nil, err } var output []string items := drive["value"].([]interface{}) for _, item := range items { itemobj := item.(map[string]interface{}) if _, ok := itemobj["file"]; !ok { output = append(output, itemobj["name"].(string)) } } return output, nil } type FileMeta struct { Path string `json:"path"` LastModified time.Time `json:"lastModified"` } func (s Graph) ListFiles(parent string) ([]FileMeta, error) { drive, err := s.listChildren(parent) if err != nil { return nil, err } var output []FileMeta items := drive["value"].([]interface{}) for _, item := range items { itemobj := item.(map[string]interface{}) if _, ok := itemobj["file"]; ok { modTime, _ := time.Parse(time.RFC3339, itemobj["lastModifiedDateTime"].(string)) output = append(output, FileMeta{ Path: path.Join(parent, itemobj["name"].(string)), LastModified: modTime, }) } } return output, nil } func (s Graph) GetChannels() (map[string]string, error) { if len(s.groupId) == 0 { return nil, errGroupIdMissing } token, err := s.getAuthoizationToken() if err != nil { return nil, err } requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/teams/%s/channels", s.groupId) req, _ := http.NewRequest("GET", requrl, nil) req.Header.Add("Authorization", token) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer func() { io.Copy(io.Discard, resp.Body) resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var raw map[string]interface{} if err = json.Unmarshal(body, &raw); err != nil { return nil, err } if errnode, ok := raw["error"]; ok { return nil, errors.New(errnode.(map[string]interface{})["message"].(string)) } if r, ok := raw["value"]; ok { valuenode := r.([]interface{}) output := make(map[string]string) for _, v := range valuenode { data := v.(map[string]interface{}) id := data["id"].(string) displayname := data["displayName"].(string) output[id] = displayname } return output, nil } return nil, errors.New("error") }