├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── client ├── client.go ├── doc.go ├── headless_client.go ├── headless_client_test.go └── web_client.go ├── common ├── doc.go └── plan.go ├── doc.go ├── go.mod ├── internal ├── doc.go └── http.go ├── scopes ├── calendars.go ├── contacts.go ├── device.go ├── directory.go ├── intune_device_management.go ├── open_id.go ├── people.go ├── scope.go └── user.go └── users ├── doc.go ├── fields.go ├── licenses.go ├── locale.go ├── mailbox.go ├── password.go ├── service.go └── user.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # Configuration file that contains parameters to access a Microsoft Graph API application for 17 | # testing purposes. This is parsed at test-time. If it is not provided, the tests will fail. 18 | # The content of this file should look something like 19 | # { 20 | # "ClientID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", 21 | # "ClientSecret": "as09djfa9sjdf", 22 | # "TenantID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", 23 | # } 24 | test.json 25 | 26 | main.go 27 | msgoraph -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mike Hockerman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | vgo build github.com/mhoc/msgoraph 3 | vgo build github.com/mhoc/msgoraph/client 4 | vgo build github.com/mhoc/msgoraph/common 5 | vgo build github.com/mhoc/msgoraph/internal 6 | vgo build github.com/mhoc/msgoraph/scopes 7 | vgo build github.com/mhoc/msgoraph/users 8 | 9 | docs: 10 | @echo "http://localhost:6060/pkg/github.com/mhoc/msgoraph/" 11 | godoc -http=:6060 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # msgoraph 2 | 3 | [![Documentation](https://godoc.org/github.com/mhoc/msgoraph?status.svg)](http://godoc.org/github.com/mhoc/msgoraph) 4 | 5 | A prototype zero dependency Go Client for [Microsoft's Graph API](https://developer.microsoft.com/en-us/graph/docs/concepts/overview). 6 | 7 | ## Disclaimers 8 | 9 | **This library is not under active development.** It (probably) works as it stands, but is very incomplete and I don't have the intention of building it out further at this time. Think of it as an academic exercise; maybe learn from it, or fork it if you'd like. 10 | 11 | This project is completely unaffiliated with Microsoft. 12 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // Client is an interface which all client types abide by. It guarantees operations around 9 | // credentials; primarily getting, initializing, and refreshing. 10 | type Client interface { 11 | Credentials() *RequestCredentials 12 | 13 | // InitializeCredentials should make the initial requests necessary to establish the first set of 14 | // authentication credentials within the Client. 15 | InitializeCredentials() error 16 | 17 | // RefreshCredentials should initiate an internal refresh of the request credentials inside this 18 | // client. This refresh should, whenever possible, check the 19 | // RequestCredentials.AccessTokenExpiresAt field to determine whether it should actually refresh 20 | // the credentials or if the credentials are still valid. 21 | RefreshCredentials() error 22 | } 23 | 24 | // RequestCredentials stores all the information necessary to authenticate a request with the 25 | // Microsoft GraphAPI 26 | type RequestCredentials struct { 27 | AccessToken string 28 | AccessTokenExpiresAt time.Time 29 | AccessTokenUpdating sync.Mutex 30 | } 31 | -------------------------------------------------------------------------------- /client/doc.go: -------------------------------------------------------------------------------- 1 | // Package client implements client state, authentication, pagination, type helpers, and method 2 | // helpers concerning accessing resources in the Microsoft Graph API. 3 | package client 4 | -------------------------------------------------------------------------------- /client/headless_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "time" 10 | 11 | "github.com/mhoc/msgoraph/scopes" 12 | ) 13 | 14 | // Headless is used to authenticate requests in the context of a backend app. This is the most 15 | // common way for applications to authenticate with the api. 16 | type Headless struct { 17 | ApplicationID string 18 | ApplicationSecret string 19 | Error error 20 | RefreshToken string 21 | RequestCredentials *RequestCredentials 22 | Scopes scopes.Scopes 23 | } 24 | 25 | // NewHeadless creates a new headless connection. 26 | func NewHeadless(applicationID string, applicationSecret string, scopes scopes.Scopes) *Headless { 27 | return &Headless{ 28 | ApplicationID: applicationID, 29 | ApplicationSecret: applicationSecret, 30 | RequestCredentials: &RequestCredentials{}, 31 | Scopes: scopes, 32 | } 33 | } 34 | 35 | // Credentials returns back the set of credentials used for every request. 36 | func (h Headless) Credentials() *RequestCredentials { 37 | return h.RequestCredentials 38 | } 39 | 40 | // InitializeCredentials will make an initial oauth2 token request for a new token. 41 | func (h Headless) InitializeCredentials() error { 42 | h.RequestCredentials.AccessTokenUpdating.Lock() 43 | defer h.RequestCredentials.AccessTokenUpdating.Unlock() 44 | if h.RequestCredentials.AccessToken != "" && h.RequestCredentials.AccessTokenExpiresAt.After(time.Now()) { 45 | return nil 46 | } 47 | tokenURI, err := url.Parse("https://login.microsoftonline.com/common/oauth2/v2.0/token") 48 | if err != nil { 49 | return err 50 | } 51 | resp, err := http.PostForm(tokenURI.String(), url.Values{ 52 | "client_id": {h.ApplicationID}, 53 | "client_secret": {h.ApplicationSecret}, 54 | "grant_type": {"client_credentials"}, 55 | "scope": {"https://graph.microsoft.com/.default"}, 56 | }) 57 | if err != nil { 58 | return err 59 | } 60 | b, err := ioutil.ReadAll(resp.Body) 61 | if err != nil { 62 | return err 63 | } 64 | var data map[string]interface{} 65 | err = json.Unmarshal(b, &data) 66 | if err != nil { 67 | return err 68 | } 69 | serverErrCode, ok := data["error"].(string) 70 | if ok { 71 | serverErr, ok := data["error_description"].(string) 72 | if ok { 73 | return fmt.Errorf("%v: %v", serverErrCode, serverErr) 74 | } 75 | return fmt.Errorf(serverErrCode) 76 | } 77 | accessToken, ok := data["access_token"].(string) 78 | if !ok || accessToken == "" { 79 | return fmt.Errorf("no access token found in response") 80 | } 81 | durationSecs, ok := data["expires_in"].(float64) 82 | if !ok || durationSecs == 0 { 83 | return fmt.Errorf("no token duration found in response") 84 | } 85 | if h.Scopes.HasScope(scopes.DelegatedOfflineAccess) { 86 | refreshToken, ok := data["refresh_token"].(string) 87 | if !ok || refreshToken == "" { 88 | return fmt.Errorf("no refresh token found in response") 89 | } 90 | h.RefreshToken = refreshToken 91 | } 92 | expiresAt := time.Now().Add(time.Duration(durationSecs) * time.Second) 93 | h.RequestCredentials.AccessToken = accessToken 94 | h.RequestCredentials.AccessTokenExpiresAt = expiresAt 95 | return nil 96 | } 97 | 98 | // RefreshCredentials will refresh the connection credentials. This just proxies through to 99 | // InitializeCredentials, because in the context of a headless appliction we should probably already 100 | // have the application secret key. 101 | func (h Headless) RefreshCredentials() error { 102 | return h.InitializeCredentials() 103 | } 104 | -------------------------------------------------------------------------------- /client/headless_client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mhoc/msgoraph/scopes" 7 | ) 8 | 9 | func TestHeadlessClientInitialization(t *testing.T) { 10 | applicationID := "" 11 | applicationSecret := "" 12 | c := NewHeadless(applicationID, applicationSecret, scopes.All(scopes.PermissionTypeApplication)) 13 | err := c.InitializeCredentials() 14 | if err != nil { 15 | t.Fatalf(err.Error()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/web_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os/exec" 12 | "runtime" 13 | "strings" 14 | "time" 15 | 16 | "github.com/mhoc/msgoraph/scopes" 17 | ) 18 | 19 | // Web is used to authenticate requests in the context of an online/user-facing app, such 20 | // as a website. This type of client is mostly useful for debugging or for command line apps where 21 | // the user configures their own app on the Microsoft Graph portal. In a normal web app, the 22 | // InitializeCredentials()->setAuthorizationCode() part of this would be called on the client, 23 | // then the code would be sent to the backend for the setAccessToken() part, given that that part 24 | // does require an ApplicationSecret. Be sure to specify DelegatedOfflineAccess as a scope if you 25 | // want refreshing to work. 26 | type Web struct { 27 | ApplicationID string 28 | ApplicationSecret string 29 | AuthorizationCode string 30 | Error error 31 | LocalhostPort int 32 | RefreshToken string 33 | RequestCredentials *RequestCredentials 34 | Scopes scopes.Scopes 35 | } 36 | 37 | // NewWeb creates a new client.Web connection. To initialize the authentication on this, call 38 | // web.InitializeCredentials() 39 | func NewWeb(applicationID string, applicationSecret string, redirectURIPort int, scopes scopes.Scopes) *Web { 40 | return &Web{ 41 | ApplicationID: applicationID, 42 | ApplicationSecret: applicationSecret, 43 | LocalhostPort: redirectURIPort, 44 | RequestCredentials: &RequestCredentials{}, 45 | Scopes: scopes, 46 | } 47 | } 48 | 49 | // Credentials returns back the set of request credentials in this client. Conforms to the 50 | // client.Client interface. 51 | func (w *Web) Credentials() *RequestCredentials { 52 | return w.RequestCredentials 53 | } 54 | 55 | // InitializeCredentials starts an oauth login flow to retrieve an authorization code, then exchange 56 | // that authorization code for an access token and (if offline access is enabled) a refresh token. 57 | func (w *Web) InitializeCredentials() error { 58 | err := w.setAuthorizationCode() 59 | if err != nil { 60 | return err 61 | } 62 | err = w.setAccessToken() 63 | return err 64 | } 65 | 66 | func (w *Web) localServer() *http.Server { 67 | srv := &http.Server{Addr: fmt.Sprintf(":%v", w.LocalhostPort)} 68 | http.HandleFunc("/login", func(wr http.ResponseWriter, r *http.Request) { 69 | err := r.ParseForm() 70 | if err != nil { 71 | w.Error = fmt.Errorf("Error while parsing form from response %s", err) 72 | return 73 | } 74 | if v, ok := r.Form["error"]; ok && len(v) > 0 { 75 | errorDescription, ok := r.Form["error_description"] 76 | if ok && len(errorDescription) > 0 { 77 | err = fmt.Errorf("%v: %v", strings.Join(v, ""), errorDescription) 78 | fmt.Fprintf(wr, "%v", err) 79 | w.Error = err 80 | } else { 81 | err = fmt.Errorf("%v", strings.Join(v, "")) 82 | fmt.Fprintf(wr, "%v", err) 83 | w.Error = err 84 | } 85 | return 86 | } 87 | code, codeOk := r.Form["code"] 88 | if len(code) > 0 && codeOk { 89 | fmt.Fprintf(wr, "authorization done. you may close this window now") 90 | w.AuthorizationCode = strings.Join(code, "") 91 | return 92 | } 93 | err = fmt.Errorf("error getting authorization code from login response") 94 | fmt.Fprintf(wr, "%v", err) 95 | w.Error = err 96 | }) 97 | go func() { 98 | if err := srv.ListenAndServe(); err != nil { 99 | // This will throw an error when we shutdown the server during the normal authorization flow 100 | // So we try to catch that error, and only return the real error if it isn't the expected 101 | // error. 102 | if !strings.Contains(err.Error(), "Server closed") { 103 | w.Error = fmt.Errorf("error on ListenAndServe: %v", err) 104 | } 105 | } 106 | }() 107 | return srv 108 | } 109 | 110 | func (w *Web) redirectURI() string { 111 | return fmt.Sprintf("http://localhost:%v/login", w.LocalhostPort) 112 | } 113 | 114 | // RefreshCredentials will attempt to refresh the access token if it is expired. This call will fail 115 | // if the original authorization was not made with a Offline scope provided. 116 | func (w *Web) RefreshCredentials() error { 117 | if !w.Scopes.HasScope(scopes.DelegatedOfflineAccess) { 118 | return fmt.Errorf("this web client was not configured for offline access and token refresh. to configure this, provide an offline scope during the initial client authorization") 119 | } 120 | if w.RefreshToken == "" { 121 | return fmt.Errorf("client.Web: no refresh token found in web client. call client.InitializeCredentials to fill this") 122 | } 123 | w.RequestCredentials.AccessTokenUpdating.Lock() 124 | defer w.RequestCredentials.AccessTokenUpdating.Unlock() 125 | if w.RequestCredentials.AccessToken != "" && w.RequestCredentials.AccessTokenExpiresAt.After(time.Now()) { 126 | return nil 127 | } 128 | tokenURI, err := url.Parse("https://login.microsoftonline.com/common/oauth2/v2.0/token") 129 | if err != nil { 130 | return err 131 | } 132 | resp, err := http.PostForm(tokenURI.String(), url.Values{ 133 | "client_id": {w.ApplicationID}, 134 | "grant_type": {"refresh_token"}, 135 | "redirect_uri": {w.redirectURI()}, 136 | "refresh_token": {w.RefreshToken}, 137 | "scope": {w.Scopes.QueryString()}, 138 | }) 139 | if err != nil { 140 | return err 141 | } 142 | b, err := ioutil.ReadAll(resp.Body) 143 | if err != nil { 144 | return err 145 | } 146 | var data map[string]interface{} 147 | err = json.Unmarshal(b, &data) 148 | if err != nil { 149 | return err 150 | } 151 | serverErrCode, ok := data["error"].(string) 152 | if ok { 153 | serverErr, ok := data["error_description"].(string) 154 | if ok { 155 | return fmt.Errorf("%v: %v", serverErrCode, serverErr) 156 | } 157 | return fmt.Errorf(serverErrCode) 158 | } 159 | accessToken, ok := data["access_token"].(string) 160 | if !ok || accessToken == "" { 161 | return fmt.Errorf("no access token found in response") 162 | } 163 | durationSecs, ok := data["expires_in"].(float64) 164 | if !ok || durationSecs == 0 { 165 | return fmt.Errorf("no token duration found in response") 166 | } 167 | refreshToken, ok := data["refresh_token"].(string) 168 | if !ok || refreshToken == "" { 169 | return fmt.Errorf("no refresh token found in response") 170 | } 171 | expiresAt := time.Now().Add(time.Duration(durationSecs) * time.Second) 172 | w.RequestCredentials.AccessToken = accessToken 173 | w.RequestCredentials.AccessTokenExpiresAt = expiresAt 174 | w.RefreshToken = refreshToken 175 | return nil 176 | } 177 | 178 | func (w *Web) setAccessToken() error { 179 | if w.AuthorizationCode == "" { 180 | return fmt.Errorf("client.Web: no access code found in web client") 181 | } 182 | w.RequestCredentials.AccessTokenUpdating.Lock() 183 | defer w.RequestCredentials.AccessTokenUpdating.Unlock() 184 | if w.RequestCredentials.AccessToken != "" && w.RequestCredentials.AccessTokenExpiresAt.After(time.Now()) { 185 | return nil 186 | } 187 | tokenURI, err := url.Parse("https://login.microsoftonline.com/common/oauth2/v2.0/token") 188 | if err != nil { 189 | return err 190 | } 191 | resp, err := http.PostForm(tokenURI.String(), url.Values{ 192 | "client_id": {w.ApplicationID}, 193 | "client_secret": {w.ApplicationSecret}, 194 | "code": {w.AuthorizationCode}, 195 | "grant_type": {"authorization_code"}, 196 | "redirect_uri": {w.redirectURI()}, 197 | "scope": {w.Scopes.QueryString()}, 198 | }) 199 | if err != nil { 200 | return err 201 | } 202 | b, err := ioutil.ReadAll(resp.Body) 203 | if err != nil { 204 | return err 205 | } 206 | var data map[string]interface{} 207 | err = json.Unmarshal(b, &data) 208 | if err != nil { 209 | return err 210 | } 211 | serverErrCode, ok := data["error"].(string) 212 | if ok { 213 | serverErr, ok := data["error_description"].(string) 214 | if ok { 215 | return fmt.Errorf("%v: %v", serverErrCode, serverErr) 216 | } 217 | return fmt.Errorf(serverErrCode) 218 | } 219 | accessToken, ok := data["access_token"].(string) 220 | if !ok || accessToken == "" { 221 | return fmt.Errorf("no access token found in response") 222 | } 223 | durationSecs, ok := data["expires_in"].(float64) 224 | if !ok || durationSecs == 0 { 225 | return fmt.Errorf("no token duration found in response") 226 | } 227 | if w.Scopes.HasScope(scopes.DelegatedOfflineAccess) { 228 | refreshToken, ok := data["refresh_token"].(string) 229 | if !ok || refreshToken == "" { 230 | return fmt.Errorf("no refresh token found in response") 231 | } 232 | w.RefreshToken = refreshToken 233 | } 234 | expiresAt := time.Now().Add(time.Duration(durationSecs) * time.Second) 235 | w.RequestCredentials.AccessToken = accessToken 236 | w.RequestCredentials.AccessTokenExpiresAt = expiresAt 237 | return nil 238 | } 239 | 240 | func (w *Web) setAuthorizationCode() error { 241 | formVals := url.Values{} 242 | formVals.Set("client_id", w.ApplicationID) 243 | formVals.Set("grant_type", "authorization_code") 244 | formVals.Set("redirect_uri", w.redirectURI()) 245 | formVals.Set("response_mode", "query") 246 | formVals.Set("response_type", "code") 247 | formVals.Set("scope", w.Scopes.QueryString()) 248 | uri, err := url.Parse("https://login.microsoftonline.com/common/oauth2/v2.0/authorize") 249 | if err != nil { 250 | return err 251 | } 252 | uri.RawQuery = formVals.Encode() 253 | switch runtime.GOOS { 254 | case "darwin": 255 | err = exec.Command("open", uri.String()).Start() 256 | case "linux": 257 | err = exec.Command("xdg-open", uri.String()).Start() 258 | case "windows": 259 | err = exec.Command("rundll32", "url.dll,FileProtocolHandler", uri.String()).Start() 260 | default: 261 | err = fmt.Errorf("unsupported platform") 262 | } 263 | if err != nil { 264 | log.Fatal(err) 265 | } 266 | server := w.localServer() 267 | running := true 268 | for running { 269 | fmt.Printf("%v", w.AuthorizationCode) 270 | if w.Error != nil || w.AuthorizationCode != "" { 271 | if err := server.Shutdown(context.TODO()); err != nil { 272 | return fmt.Errorf("error on server shutdown: %v", err) 273 | } 274 | if w.Error != nil { 275 | return w.Error 276 | } 277 | return nil 278 | } 279 | } 280 | return nil 281 | } 282 | -------------------------------------------------------------------------------- /common/doc.go: -------------------------------------------------------------------------------- 1 | // Package common implements common types and logic shared between multiple verticals of the 2 | // Microsoft Graph API 3 | package common 4 | -------------------------------------------------------------------------------- /common/plan.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // AssignedPlan property of both user entity and organization entity is a 4 | // collection of assignedPlan 5 | type AssignedPlan struct { 6 | AssignedDateTime string `json:"assignedDateTime"` 7 | CapabilityStatus string `json:"capabilityStatus"` 8 | Service string `json:"service"` 9 | ServicePlanID string `json:"servicePlanId"` 10 | } 11 | 12 | // ProvisionedPlan The provisionedPlans property of the user entity and the 13 | // organization entity is a collection of provisionedPlan. 14 | type ProvisionedPlan struct { 15 | CapabilityStatus string `json:"capabilityStatus"` 16 | ProvisioningStatus string `json:"provisioningStatus"` 17 | Service string `json:"service"` 18 | } 19 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package msgoraph implements a Go interface for the Microsoft Graph API 2 | package msgoraph 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mhoc/msgoraph 2 | -------------------------------------------------------------------------------- /internal/doc.go: -------------------------------------------------------------------------------- 1 | // Package internal contains logic which should rarely be accessed by consumers of this library, 2 | // primarily around forming HTTP/odata calls to the Microsoft Graph API. 3 | package internal 4 | -------------------------------------------------------------------------------- /internal/http.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | 12 | "github.com/mhoc/msgoraph/client" 13 | ) 14 | 15 | const ( 16 | // GraphAPIRootURL is the root url that the Graph API is hosted on. 17 | GraphAPIRootURL = "https://graph.microsoft.com/" 18 | ) 19 | 20 | // BasicGraphRequest is similar to GraphRequest, but it assumes an already fully formed url and no 21 | // body. This is primarily useful for methods that need to pagniate; it just makes that a little bit 22 | // easier. 23 | func BasicGraphRequest(client client.Client, method string, url string) ([]byte, error) { 24 | req, err := http.NewRequest(method, url, nil) 25 | if err != nil { 26 | return nil, err 27 | } 28 | err = client.RefreshCredentials() 29 | if err != nil { 30 | return nil, err 31 | } 32 | req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", client.Credentials().AccessToken)) 33 | req.Header.Add("Content-Type", "application/json") 34 | resp, err := http.DefaultClient.Do(req) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return ioutil.ReadAll(resp.Body) 39 | } 40 | 41 | // GraphRequest creates and executes a new http request against the Graph API. The path 42 | // provided should be the entire path of the url, including the version specifier. It returns the 43 | // response body, along with any errors that might occur during the request process. 44 | func GraphRequest(client client.Client, method string, path string, params url.Values, body interface{}) ([]byte, error) { 45 | var graphURL string 46 | if len(params) > 0 { 47 | graphURL = fmt.Sprintf("%v%v?%v", GraphAPIRootURL, path, params.Encode()) 48 | } else { 49 | graphURL = fmt.Sprintf("%v%v", GraphAPIRootURL, path) 50 | } 51 | var bodyBuffered io.Reader 52 | if body != nil { 53 | j, err := json.Marshal(body) 54 | if err != nil { 55 | return nil, err 56 | } 57 | bodyBuffered = bytes.NewBuffer(j) 58 | } 59 | req, err := http.NewRequest(method, graphURL, bodyBuffered) 60 | if err != nil { 61 | return nil, err 62 | } 63 | err = client.RefreshCredentials() 64 | if err != nil { 65 | return nil, err 66 | } 67 | req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", client.Credentials().AccessToken)) 68 | req.Header.Add("Content-Type", "application/json") 69 | resp, err := http.DefaultClient.Do(req) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return ioutil.ReadAll(resp.Body) 74 | } 75 | -------------------------------------------------------------------------------- /scopes/calendars.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | var ( 4 | // ApplicationCalendarsRead Read calendars in all mailboxes 5 | ApplicationCalendarsRead = Scope{ 6 | AdminConsentRequired: true, 7 | Description: "Allows the app to read events of all calendars without a signed-in user.", 8 | DisplayString: "Read calendars in all mailboxes", 9 | Permission: "Calendars.Read", 10 | Type: PermissionTypeApplication, 11 | } 12 | // ApplicationCalendarsReadWrite Read and write calendars in all mailboxes 13 | ApplicationCalendarsReadWrite = Scope{ 14 | AdminConsentRequired: true, 15 | Description: "Allows the app to create, read, update, and delete events of all calendars without a signed-in user.", 16 | DisplayString: "Read and write calendars in all mailboxes", 17 | Permission: "Calendars.ReadWrite", 18 | Type: PermissionTypeApplication, 19 | } 20 | // DelegatedCalendarsRead Read user calendars 21 | DelegatedCalendarsRead = Scope{ 22 | Description: "Allows the app to read events in user calendars.", 23 | DisplayString: "Read user calendars", 24 | Permission: "Calendars.Read", 25 | Type: PermissionTypeDelegated, 26 | } 27 | // DelegatedCalendarsReadShared Read user and shared calendars 28 | DelegatedCalendarsReadShared = Scope{ 29 | Description: "Allows the app to read events in all calendars that the user can access, including delegate and shared calendars.", 30 | DisplayString: "Read user and shared calendars", 31 | Permission: "Calendars.Read.Shared", 32 | Type: PermissionTypeDelegated, 33 | } 34 | // DelegatedCalendarsReadWrite Have full access to user calendars 35 | DelegatedCalendarsReadWrite = Scope{ 36 | Description: "Allows the app to create, read, update, and delete events in user calendars.", 37 | DisplayString: "Have full access to user calendars", 38 | Permission: "Calendars.ReadWrite", 39 | Type: PermissionTypeDelegated, 40 | } 41 | // DelegatedCalendarsReadWriteShared Read and write user and shared calendars 42 | DelegatedCalendarsReadWriteShared = Scope{ 43 | Description: "Allows the app to create, read, update and delete events in all calendars the user has permissions to access. This includes delegate and shared calendars.", 44 | DisplayString: "Read and write user and shared calendars", 45 | Permission: "Calendars.ReadWrite.Shared", 46 | Type: PermissionTypeDelegated, 47 | } 48 | ) 49 | -------------------------------------------------------------------------------- /scopes/contacts.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | var ( 4 | // ApplicationContactsRead Read contacts in all mailboxes 5 | ApplicationContactsRead = Scope{ 6 | AdminConsentRequired: true, 7 | Description: "Allows the app to read all contacts in all mailboxes without a signed-in user.", 8 | DisplayString: "Read contacts in all mailboxes", 9 | Permission: "Contacts.Read", 10 | Type: PermissionTypeApplication, 11 | } 12 | // ApplicationContactsReadWrite Read and write contacts in all mailboxes 13 | ApplicationContactsReadWrite = Scope{ 14 | AdminConsentRequired: true, 15 | Description: "Allows the app to create, read, update, and delete all contacts in all mailboxes without a signed-in user.", 16 | DisplayString: "Read and write contacts in all mailboxes", 17 | Permission: "Contacts.ReadWrite", 18 | Type: PermissionTypeApplication, 19 | } 20 | // DelegatedContactsRead Read user contacts 21 | DelegatedContactsRead = Scope{ 22 | Description: "Allows the app to read user contacts.", 23 | DisplayString: "Read user contacts", 24 | Permission: "Contacts.Read", 25 | Type: PermissionTypeDelegated, 26 | } 27 | // DelegatedContactsReadShared Read user and shared contacts 28 | DelegatedContactsReadShared = Scope{ 29 | Description: "Allows the app to read contacts that the user has permissions to access, including the user's own and shared contacts.", 30 | DisplayString: "Read user and shared contacts", 31 | Permission: "Contacts.Read.Shared", 32 | Type: PermissionTypeDelegated, 33 | } 34 | // DelegatedContactsReadWrite Have full access to user contacts 35 | DelegatedContactsReadWrite = Scope{ 36 | Description: "Allows the app to create, read, update, and delete user contacts.", 37 | DisplayString: "Have full access to user contacts", 38 | Permission: "Contacts.ReadWrite", 39 | Type: PermissionTypeDelegated, 40 | } 41 | // DelegatedContactsReadWriteShared Read and write user and shared contacts 42 | DelegatedContactsReadWriteShared = Scope{ 43 | Description: "Allows the app to create, read, update and delete contacts that the user has permissions to, including the user's own and shared contacts.", 44 | DisplayString: "Read and write user and shared contacts", 45 | Permission: "Contacts.ReadWrite.Shared", 46 | Type: PermissionTypeDelegated, 47 | } 48 | ) 49 | -------------------------------------------------------------------------------- /scopes/device.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | var ( 4 | // ApplicationDeviceReadWriteAll Read and write devices 5 | ApplicationDeviceReadWriteAll = Scope{ 6 | AdminConsentRequired: true, 7 | Description: "Allows the app to read and write all device properties without a signed in user. Does not allow device creation, device deletion, or update of device alternative security identifiers.", 8 | DisplayString: "Read and write devices", 9 | Permission: "Device.ReadWrite.All", 10 | Type: PermissionTypeApplication, 11 | } 12 | // DelegatedDeviceRead Read user devices 13 | DelegatedDeviceRead = Scope{ 14 | Description: "Allows the app to read a user's list of devices on behalf of the signed-in user.", 15 | DisplayString: "Read user devices", 16 | Permission: "Device.Read", 17 | Type: PermissionTypeDelegated, 18 | } 19 | // DelegatedDeviceCommand Communicate with user devices 20 | DelegatedDeviceCommand = Scope{ 21 | Description: "Allows the app to launch another app or communicate with another app on a user's device on behalf of the signed-in user.", 22 | DisplayString: "Communicate with user devices", 23 | Permission: "Device.Command", 24 | Type: PermissionTypeDelegated, 25 | } 26 | ) 27 | -------------------------------------------------------------------------------- /scopes/directory.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | var ( 4 | // ApplicationDirectoryReadAll Read directory data 5 | ApplicationDirectoryReadAll = Scope{ 6 | AdminConsentRequired: true, 7 | DisplayString: "Read directory data", 8 | Description: "Allows the app to read data in your organization's directory, such as users, groups and apps, without a signed-in user.", 9 | Permission: "Directory.Read.All", 10 | Type: PermissionTypeApplication, 11 | } 12 | // ApplicationDirectoryReadWriteAll Read and write directory data 13 | ApplicationDirectoryReadWriteAll = Scope{ 14 | AdminConsentRequired: true, 15 | DisplayString: "Read and write directory data", 16 | Description: "Allows the app to read and write data in your organization's directory, such as users, and groups, without a signed-in user. Does not allow user or group deletion.", 17 | Permission: "Directory.ReadWrite.All", 18 | Type: PermissionTypeApplication, 19 | } 20 | // DelegatedDirectoryReadAll Read directory data 21 | DelegatedDirectoryReadAll = Scope{ 22 | AdminConsentRequired: true, 23 | DisplayString: "Read directory data", 24 | Description: "Allows the app to read data in your organization's directory, such as users, groups and apps. Note: Users may consent to applications that require this permission if the application is registered in their own organization’s tenant.", 25 | Permission: "Directory.Read.All", 26 | Type: PermissionTypeDelegated, 27 | } 28 | // DelegatedDirectoryReadWriteAll Read and write directory data 29 | DelegatedDirectoryReadWriteAll = Scope{ 30 | AdminConsentRequired: true, 31 | DisplayString: "Read and write directory data", 32 | Description: "Allows the app to read and write data in your organization's directory, such as users, and groups. It does not allow the app to delete users or groups, or reset user passwords.", 33 | Permission: "Directory.ReadWrite.All", 34 | Type: PermissionTypeDelegated, 35 | } 36 | // DelegatedDirectoryAccessAsUser Access directory as the signed-in user 37 | DelegatedDirectoryAccessAsUser = Scope{ 38 | AdminConsentRequired: true, 39 | DisplayString: "Access directory as the signed-in user", 40 | Description: "Allows the app to have the same access to information in the directory as the signed-in user.", 41 | Permission: "Directory.AccessAsUser.All", 42 | Type: PermissionTypeDelegated, 43 | } 44 | ) 45 | -------------------------------------------------------------------------------- /scopes/intune_device_management.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | var ( 4 | // DelegatedDeviceManagementAppsReadAll Read Microsoft Intune apps 5 | DelegatedDeviceManagementAppsReadAll = Scope{ 6 | AdminConsentRequired: true, 7 | Description: "Allows the app to read the properties, group assignments and status of apps, app configurations and app protection policies managed by Microsoft Intune.", 8 | DisplayString: "Read Microsoft Intune apps", 9 | Permission: "DeviceManagementApps.Read.All", 10 | Type: PermissionTypeDelegated, 11 | } 12 | // DelegatedDeviceManagementAppsReadWriteAll Read and write Microsoft Intune apps 13 | DelegatedDeviceManagementAppsReadWriteAll = Scope{ 14 | AdminConsentRequired: true, 15 | Description: "Allows the app to read and write the properties, group assignments and status of apps, app configurations and app protection policies managed by Microsoft Intune.", 16 | DisplayString: "Read and write Microsoft Intune apps", 17 | Permission: "DeviceManagementApps.ReadWrite.All", 18 | Type: PermissionTypeDelegated, 19 | } 20 | // DelegatedDeviceManagementConfigurationReadAll Read Microsoft Intune device configuration and policies 21 | DelegatedDeviceManagementConfigurationReadAll = Scope{ 22 | AdminConsentRequired: true, 23 | Description: "Allows the app to read properties of Microsoft Intune-managed device configuration and device compliance policies and their assignment to groups.", 24 | DisplayString: "Read Microsoft Intune device configuration and policies", 25 | Permission: "DeviceManagementConfiguration.Read.All", 26 | Type: PermissionTypeDelegated, 27 | } 28 | // DelegatedDeviceManagementConfigurationReadWriteAll "Read and write Microsoft Intune device configuration and policies" 29 | DelegatedDeviceManagementConfigurationReadWriteAll = Scope{ 30 | AdminConsentRequired: true, 31 | Description: "Allows the app to read and write properties of Microsoft Intune-managed device configuration and device compliance policies and their assignment to groups.", 32 | DisplayString: "Read and write Microsoft Intune device configuration and policies", 33 | Permission: "DeviceManagementConfiguration.ReadWrite.All", 34 | Type: PermissionTypeDelegated, 35 | } 36 | // DelegatedDeviceManagementManagedDevicesPrivilegedOperationsAll Perform user-impacting remote actions on Microsoft Intune devices 37 | DelegatedDeviceManagementManagedDevicesPrivilegedOperationsAll = Scope{ 38 | AdminConsentRequired: true, 39 | Description: "Allows the app to perform remote high impact actions such as wiping the device or resetting the passcode on devices managed by Microsoft Intune.", 40 | DisplayString: "Perform user-impacting remote actions on Microsoft Intune devices", 41 | Permission: "DeviceManagementManagedDevices.PrivilegedOperations.All", 42 | Type: PermissionTypeDelegated, 43 | } 44 | // DelegatedDeviceManagementManagedDevicesReadAll Read Microsoft Intune devices 45 | DelegatedDeviceManagementManagedDevicesReadAll = Scope{ 46 | AdminConsentRequired: true, 47 | Description: "Allows the app to read the properties of devices managed by Microsoft Intune.", 48 | DisplayString: "Read Microsoft Intune devices", 49 | Permission: "DeviceManagementManagedDevices.Read.All", 50 | Type: PermissionTypeDelegated, 51 | } 52 | // DelegatedDeviceManagementManagedDevicesReadWriteAll Read and write Microsoft Intune devices 53 | DelegatedDeviceManagementManagedDevicesReadWriteAll = Scope{ 54 | AdminConsentRequired: true, 55 | Description: "Allows the app to read and write the properties of devices managed by Microsoft Intune. Does not allow high impact operations such as remote wipe and password reset on the device’s owner.", 56 | DisplayString: "Read and write Microsoft Intune devices", 57 | Permission: "DeviceManagementManagedDevices.ReadWrite.All", 58 | Type: PermissionTypeDelegated, 59 | } 60 | // DelegatedDeviceManagementRBACReadAll Read Microsoft Intune RBAC settings 61 | DelegatedDeviceManagementRBACReadAll = Scope{ 62 | AdminConsentRequired: true, 63 | Description: "Allows the app to read the properties relating to the Microsoft Intune Role-Based Access Control (RBAC) settings.", 64 | DisplayString: "Read Microsoft Intune RBAC settings", 65 | Permission: "DeviceManagementRBAC.Read.All", 66 | Type: PermissionTypeDelegated, 67 | } 68 | // DelegatedDeviceManagementRBACReadWriteAll Read and write Microsoft Intune RBAC settings 69 | DelegatedDeviceManagementRBACReadWriteAll = Scope{ 70 | AdminConsentRequired: true, 71 | Description: "Allows the app to read and write the properties relating to the Microsoft Intune Role-Based Access Control (RBAC) settings.", 72 | DisplayString: "Read and write Microsoft Intune RBAC settings", 73 | Permission: "DeviceManagementRBAC.ReadWrite.All", 74 | Type: PermissionTypeDelegated, 75 | } 76 | // DelegatedDeviceManagementServiceConfigReadAll Read Microsoft Intune configuration 77 | DelegatedDeviceManagementServiceConfigReadAll = Scope{ 78 | AdminConsentRequired: true, 79 | Description: "Allows the app to read Intune service properties including device enrollment and third party service connection configuration.", 80 | DisplayString: "Read Microsoft Intune configuration", 81 | Permission: "DeviceManagementServiceConfig.Read.All", 82 | Type: PermissionTypeDelegated, 83 | } 84 | // DelegatedDeviceManagementServiceConfigReadWriteAll Read and write Microsoft Intune configuration 85 | DelegatedDeviceManagementServiceConfigReadWriteAll = Scope{ 86 | AdminConsentRequired: true, 87 | Description: "Allows the app to read and write Microsoft Intune service properties including device enrollment and third party service connection configuration.", 88 | DisplayString: "Read and write Microsoft Intune configuration", 89 | Permission: "DeviceManagementServiceConfig.ReadWrite.All", 90 | Type: PermissionTypeDelegated, 91 | } 92 | ) 93 | -------------------------------------------------------------------------------- /scopes/open_id.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | var ( 4 | // DelegatedEmail View users' email address 5 | DelegatedEmail = Scope{ 6 | Description: "Allows the app to read your users' primary email address.", 7 | DisplayString: "View users' email address", 8 | Permission: "email", 9 | Type: PermissionTypeDelegated, 10 | } 11 | // DelegatedOfflineAccess Access user's data anytime 12 | DelegatedOfflineAccess = Scope{ 13 | Description: "Allows the app to read and update user data, even when they are not currently using the app.", 14 | DisplayString: "Access user's data anytime", 15 | Permission: "offline_access", 16 | Type: PermissionTypeDelegated, 17 | } 18 | // DelegatedOpenID Sign users in 19 | DelegatedOpenID = Scope{ 20 | Description: "Allows users to sign in to the app with their work or school accounts and allows the app to see basic user profile information.", 21 | DisplayString: "Sign users in", 22 | Permission: "openid", 23 | Type: PermissionTypeDelegated, 24 | } 25 | // DelegatedProfile View users' basic profile 26 | DelegatedProfile = Scope{ 27 | Description: "Allows the app to see your users' basic profile (name, picture, user name).", 28 | DisplayString: "View users' basic profile", 29 | Permission: "profile", 30 | Type: PermissionTypeDelegated, 31 | } 32 | ) 33 | -------------------------------------------------------------------------------- /scopes/people.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | var ( 4 | // ApplicationPeopleReadAll Read all users' relevant people lists 5 | ApplicationPeopleReadAll = Scope{ 6 | AdminConsentRequired: true, 7 | Description: "Allows the app to read a scored list of people relevant to the signed-in user or other users in the signed-in user's organization. The list can include local contacts, contacts from social networking or your organization's directory, and people from recent communications (such as email and Skype). Also allows the app to search the entire directory of the signed-in user's organization.", 8 | DisplayString: "Read all users' relevant people lists", 9 | Permission: "People.Read.All", 10 | Type: PermissionTypeApplication, 11 | } 12 | // DelegatedPeopleRead Read users' relevant people lists 13 | DelegatedPeopleRead = Scope{ 14 | Description: "Allows the app to read a scored list of people relevant to the signed-in user. The list can include local contacts, contacts from social networking or your organization's directory, and people from recent communications (such as email and Skype).", 15 | DisplayString: "Read users' relevant people lists", 16 | Permission: "People.Read", 17 | Type: PermissionTypeDelegated, 18 | } 19 | // DelegatedPeopleReadAll Read all users' relevant people lists 20 | DelegatedPeopleReadAll = Scope{ 21 | AdminConsentRequired: true, 22 | Description: "Allows the app to read a scored list of people relevant to the signed-in user or other users in the signed-in user's organization. The list can include local contacts, contacts from social networking or your organization's directory, and people from recent communications (such as email and Skype). Also allows the app to search the entire directory of the signed-in user's organization.", 23 | DisplayString: "Read all users' relevant people lists", 24 | Permission: "People.Read.All", 25 | Type: PermissionTypeDelegated, 26 | } 27 | ) 28 | -------------------------------------------------------------------------------- /scopes/scope.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // PermissionType defines whether a permission is delegated or application. 8 | type PermissionType string 9 | 10 | // Scope contains information about the OAuth scopes exported by the Graph API. This includes the 11 | // permission itself, as well as information about displaying it in a UI if you ever need to. This 12 | // information is all pulled from the Graph API documentation at the time of this file's commit, 13 | // and could change at any point (https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference). 14 | type Scope struct { 15 | AdminConsentRequired bool 16 | DisplayString string 17 | Description string 18 | Permission string 19 | Type PermissionType 20 | } 21 | 22 | // Scopes is an alias to an array of scopes, with some additional logic on the type to make interfacing 23 | // with the collection easier. 24 | type Scopes []Scope 25 | 26 | const ( 27 | // PermissionTypeApplication application permissions 28 | PermissionTypeApplication = "application" 29 | // PermissionTypeAll both application and delegated permission types. This is mostly used 30 | // internally for querying and building lists on all the permissions in the graph api. 31 | PermissionTypeAll = "all" 32 | // PermissionTypeDelegated delegated permissions 33 | PermissionTypeDelegated = "delegated" 34 | ) 35 | 36 | // All returns a list of every permission in the graph api by permission type. 37 | func All(typ PermissionType) Scopes { 38 | var scopes Scopes 39 | if typ == PermissionTypeAll || typ == PermissionTypeApplication { 40 | scopes = append(scopes, []Scope{ 41 | ApplicationContactsRead, 42 | ApplicationContactsReadWrite, 43 | ApplicationContactsRead, 44 | ApplicationContactsReadWrite, 45 | ApplicationDeviceReadWriteAll, 46 | ApplicationDirectoryReadAll, 47 | ApplicationDirectoryReadWriteAll, 48 | ApplicationPeopleReadAll, 49 | ApplicationUserReadAll, 50 | ApplicationUserReadWriteAll, 51 | ApplicationUserInviteAll, 52 | ApplicationUserExportAll, 53 | }...) 54 | } 55 | if typ == PermissionTypeAll || typ == PermissionTypeDelegated { 56 | scopes = append(scopes, []Scope{ 57 | DelegatedCalendarsRead, 58 | DelegatedCalendarsReadShared, 59 | DelegatedCalendarsReadWrite, 60 | DelegatedCalendarsReadWriteShared, 61 | DelegatedContactsRead, 62 | DelegatedCalendarsRead, 63 | DelegatedCalendarsReadShared, 64 | DelegatedCalendarsReadWrite, 65 | DelegatedCalendarsReadWriteShared, 66 | DelegatedDeviceRead, 67 | DelegatedDeviceCommand, 68 | DelegatedDirectoryReadAll, 69 | DelegatedDirectoryReadWriteAll, 70 | DelegatedDirectoryAccessAsUser, 71 | DelegatedDeviceManagementAppsReadAll, 72 | DelegatedDeviceManagementAppsReadWriteAll, 73 | DelegatedDeviceManagementConfigurationReadAll, 74 | DelegatedDeviceManagementConfigurationReadWriteAll, 75 | DelegatedDeviceManagementManagedDevicesPrivilegedOperationsAll, 76 | DelegatedDeviceManagementManagedDevicesReadAll, 77 | DelegatedDeviceManagementManagedDevicesReadWriteAll, 78 | DelegatedDeviceManagementRBACReadAll, 79 | DelegatedDeviceManagementRBACReadWriteAll, 80 | DelegatedDeviceManagementServiceConfigReadAll, 81 | DelegatedDeviceManagementServiceConfigReadWriteAll, 82 | DelegatedEmail, 83 | DelegatedOfflineAccess, 84 | DelegatedOpenID, 85 | DelegatedProfile, 86 | DelegatedPeopleRead, 87 | DelegatedPeopleReadAll, 88 | DelegatedUserRead, 89 | DelegatedUserReadWrite, 90 | DelegatedUserReadBasicAll, 91 | DelegatedUserReadAll, 92 | DelegatedUserReadWriteAll, 93 | DelegatedUserInviteAll, 94 | DelegatedUserExportAll, 95 | }...) 96 | } 97 | return scopes 98 | } 99 | 100 | // Resolve takes a list of space separate scopes, also known as the output of QueryString, and 101 | // reassembles them into a list of valid Scopes. 102 | // You also need to specify whether you want these returned scopes to be delegated or application 103 | // scopes, given that many permission names are shared between delegated and application 104 | // permissions. 105 | // This function will panic if PermissionTypeAll is provided. 106 | func Resolve(scopeList string, preferType PermissionType) Scopes { 107 | if preferType == PermissionTypeAll { 108 | panic("must provide either PermissionTypeApplication or PermissionTypeDelegated") 109 | } 110 | allScopes := All(preferType) 111 | each := strings.Split(scopeList, " ") 112 | var foundScopes Scopes 113 | for _, s := range each { 114 | ss := allScopes.Find(s) 115 | if ss != nil { 116 | foundScopes = append(foundScopes, *ss) 117 | } 118 | } 119 | return foundScopes 120 | } 121 | 122 | // Find will locate the scope within the given list of scopes with the given permission name. To 123 | // search across all available scopes, chain it with All().Find("user.read") 124 | func (s Scopes) Find(permissionName string) *Scope { 125 | for _, ss := range s { 126 | if strings.ToLower(ss.Permission) == strings.ToLower(permissionName) { 127 | return &ss 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | // HasScope returns true if the list of scopes contains the requested scope. 134 | func (s Scopes) HasScope(scope Scope) bool { 135 | for _, iS := range s { 136 | if iS.Permission == scope.Permission && iS.Type == scope.Type { 137 | return true 138 | } 139 | } 140 | return false 141 | } 142 | 143 | // QueryString will turn a list of scopes into a query string suitable for consumption by 144 | // the Graph API. Something like "offline_access user.read mail.read". 145 | func (s Scopes) QueryString() string { 146 | qs := "" 147 | for _, scope := range s { 148 | qs += scope.Permission + " " 149 | } 150 | return qs 151 | } 152 | -------------------------------------------------------------------------------- /scopes/user.go: -------------------------------------------------------------------------------- 1 | package scopes 2 | 3 | var ( 4 | // ApplicationUserReadAll Read all users' full profiles 5 | ApplicationUserReadAll = Scope{ 6 | AdminConsentRequired: true, 7 | Description: "Allows the app to read the full set of profile properties, group membership, reports and managers of other users in your organization, without a signed-in user.", 8 | DisplayString: "Read all users' full profiles", 9 | Permission: "User.Read.All", 10 | Type: PermissionTypeApplication, 11 | } 12 | // ApplicationUserReadWriteAll Read and write all users' full profiles 13 | ApplicationUserReadWriteAll = Scope{ 14 | AdminConsentRequired: true, 15 | Description: "Allows the app to read and write the full set of profile properties, group membership, reports and managers of other users in your organization, without a signed-in user. Also allows the app to create and delete non-administrative users. Does not allow reset of user passwords.", 16 | DisplayString: "Read and write all users' full profiles", 17 | Permission: "User.ReadWrite.All", 18 | Type: PermissionTypeApplication, 19 | } 20 | // ApplicationUserInviteAll Invite guest users to the organization 21 | ApplicationUserInviteAll = Scope{ 22 | AdminConsentRequired: true, 23 | Description: "Allows the app to invite guest users to your organization, without a signed-in user.", 24 | DisplayString: "Invite guest users to the organization", 25 | Permission: "User.Invite.All", 26 | Type: PermissionTypeApplication, 27 | } 28 | // ApplicationUserExportAll Export users' data 29 | ApplicationUserExportAll = Scope{ 30 | AdminConsentRequired: true, 31 | Description: "Allows the app to export organizational users' data, without a signed-in user.", 32 | DisplayString: "Export users' data", 33 | Permission: "User.Export.All", 34 | Type: PermissionTypeApplication, 35 | } 36 | // DelegatedUserRead Sign-in and read user profile 37 | DelegatedUserRead = Scope{ 38 | Description: "Allows users to sign-in to the app, and allows the app to read the profile of signed-in users. It also allows the app to read basic company information of signed-in users.", 39 | DisplayString: "Sign-in and read user profile", 40 | Permission: "User.Read", 41 | Type: PermissionTypeDelegated, 42 | } 43 | // DelegatedUserReadWrite Read and write access to user profile 44 | DelegatedUserReadWrite = Scope{ 45 | Description: "Allows the app to read your profile. It also allows the app to update your profile information on your behalf.", 46 | DisplayString: "Read and write access to user profile", 47 | Permission: "User.ReadWrite", 48 | Type: PermissionTypeDelegated, 49 | } 50 | // DelegatedUserReadBasicAll Read all users' basic profiles 51 | DelegatedUserReadBasicAll = Scope{ 52 | Description: "Allows the app to read a basic set of profile properties of other users in your organization on behalf of the signed-in user. This includes display name, first and last name, email address and photo.", 53 | DisplayString: "Read all users' basic profiles", 54 | Permission: "User.ReadBasic.All", 55 | Type: PermissionTypeDelegated, 56 | } 57 | // DelegatedUserReadAll Read all users' full profiles 58 | DelegatedUserReadAll = Scope{ 59 | AdminConsentRequired: true, 60 | Description: "Allows the app to read the full set of profile properties, reports, and managers of other users in your organization, on behalf of the signed-in user.", 61 | DisplayString: "Read all users' full profiles", 62 | Permission: "User.Read.All", 63 | Type: PermissionTypeDelegated, 64 | } 65 | // DelegatedUserReadWriteAll Read and write all users' full profiles 66 | DelegatedUserReadWriteAll = Scope{ 67 | AdminConsentRequired: true, 68 | Description: "Allows the app to read and write the full set of profile properties, reports, and managers of other users in your organization, on behalf of the signed-in user. Also allows the app to create and delete users as well as reset user passwords on behalf of the signed-in user.", 69 | DisplayString: "Read and write all users' full profiles", 70 | Permission: "User.ReadWrite.All", 71 | Type: PermissionTypeDelegated, 72 | } 73 | // DelegatedUserInviteAll Invite guest users to the organization 74 | DelegatedUserInviteAll = Scope{ 75 | AdminConsentRequired: true, 76 | Description: "Allows the app to invite guest users to your organization, on behalf of the signed-in user.", 77 | DisplayString: "Invite guest users to the organization", 78 | Permission: "User.Invite.All", 79 | Type: PermissionTypeDelegated, 80 | } 81 | // DelegatedUserExportAll Export users' data 82 | DelegatedUserExportAll = Scope{ 83 | AdminConsentRequired: true, 84 | Description: "Allows the app to export an organizational user's data, when performed by a Company Administrator.", 85 | DisplayString: "Export users' data", 86 | Permission: "User.Export.All", 87 | Type: PermissionTypeDelegated, 88 | } 89 | ) 90 | -------------------------------------------------------------------------------- /users/doc.go: -------------------------------------------------------------------------------- 1 | // Package users implements functionality surrounding accessing and mutating user data in 2 | // the Microsoft Graph API. 3 | package users 4 | -------------------------------------------------------------------------------- /users/fields.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | // Field can be provided to the user request functions to select which Fields 4 | // are provided by Microsoft for each user. There's one for every root Field on the user object 5 | // and they match up perfectly with the json names above. All of these have little comments 6 | // on them for the purpose of supressing godoc linter warnings. 7 | type Field string 8 | 9 | const ( 10 | // FieldID id 11 | FieldID Field = "id" 12 | // FieldAboutMe aboutMe 13 | FieldAboutMe Field = "aboutMe" 14 | // FieldAccountEnabled accountsEnabled 15 | FieldAccountEnabled Field = "accountEnabled" 16 | // FieldAssignedLicenses assignedLicenses 17 | FieldAssignedLicenses Field = "assignedLicenses" 18 | // FieldAssignedPlans assignedPlans 19 | FieldAssignedPlans Field = "assignedPlans" 20 | // FieldBirthday birthday 21 | FieldBirthday Field = "birthday" 22 | // FieldBusinessPhones businessPhones 23 | FieldBusinessPhones Field = "businessPhones" 24 | // FieldCity city 25 | FieldCity Field = "city" 26 | // FieldCompanyName companyName 27 | FieldCompanyName Field = "companyName" 28 | // FieldCountry country 29 | FieldCountry Field = "country" 30 | // FieldDepartment department 31 | FieldDepartment Field = "department" 32 | // FieldDisplayName displayName 33 | FieldDisplayName Field = "displayName" 34 | // FieldGivenName givenName 35 | FieldGivenName Field = "givenName" 36 | // FieldHireDate hireDate 37 | FieldHireDate Field = "hireDate" 38 | // FieldIMAddresses imAddresses 39 | FieldIMAddresses Field = "imAddresses" 40 | // FieldInterests interests 41 | FieldInterests Field = "interests" 42 | // FieldJobTitle jobTitle 43 | FieldJobTitle Field = "jobTitle" 44 | // FieldMail mail 45 | FieldMail Field = "mail" 46 | // FieldMailboxSettings mailboxSettings 47 | FieldMailboxSettings Field = "mailboxSettings" 48 | // FieldMailNickname mailNickname 49 | FieldMailNickname Field = "mailNickname" 50 | // FieldMobilePhone mobilePhone 51 | FieldMobilePhone Field = "mobilePhone" 52 | // FieldMySite mySite 53 | FieldMySite Field = "mySite" 54 | // FieldOfficeLocation officeLocation 55 | FieldOfficeLocation Field = "officeLocation" 56 | // FieldOnPremisesImmutableID onPremisesImmutableId 57 | FieldOnPremisesImmutableID Field = "onPremisesImmutableId" 58 | // FieldOnPremisesLastSyncDateTime onPremisesLastSyncDateTime 59 | FieldOnPremisesLastSyncDateTime Field = "onPremisesLastSyncDateTime" 60 | // FieldOnPremisesSecurityIdentifier onPremisesSecurityIdentifier 61 | FieldOnPremisesSecurityIdentifier Field = "onPremisesSecurityIdentifier" 62 | // FieldOnPremisesSyncEnabled onPremisesSyncEnabled 63 | FieldOnPremisesSyncEnabled Field = "onPremisesSyncEnabled" 64 | // FieldPasswordPolicies passwordPolicies 65 | FieldPasswordPolicies Field = "passwordPolicies" 66 | // FieldPasswordProfile passwordProfile 67 | FieldPasswordProfile Field = "passwordProfile" 68 | // FieldPastProjects pastProjects 69 | FieldPastProjects Field = "pastProjects" 70 | // FieldPostalCode postalCode 71 | FieldPostalCode Field = "postalCode" 72 | // FieldPreferredLanguage preferredLanguage 73 | FieldPreferredLanguage Field = "preferredLanguage" 74 | // FieldPreferredName preferredName 75 | FieldPreferredName Field = "preferredName" 76 | // FieldProvisionedPlans provisionedPlans 77 | FieldProvisionedPlans Field = "provisionedPlans" 78 | // FieldProxyAddresses proxyAddresses 79 | FieldProxyAddresses Field = "proxyAddresses" 80 | // FieldResponsibilities responsibilities 81 | FieldResponsibilities Field = "responsibilities" 82 | // FieldSchools schools 83 | FieldSchools Field = "schools" 84 | // FieldSkills skills 85 | FieldSkills Field = "skills" 86 | // FieldState state 87 | FieldState Field = "state" 88 | // FieldStreetAddress streetAddress 89 | FieldStreetAddress Field = "streetAddress" 90 | // FieldSurname surname 91 | FieldSurname Field = "surname" 92 | // FieldUsageLocation usageLocation 93 | FieldUsageLocation Field = "usageLocation" 94 | // FieldUserPrincipalName userPrincipalName 95 | FieldUserPrincipalName Field = "userPrincipalName" 96 | // FieldUserType userType 97 | FieldUserType Field = "userType" 98 | ) 99 | 100 | var ( 101 | // UserAllFields specifies every user field available for selection in api calls. 102 | UserAllFields = []Field{ 103 | FieldID, 104 | FieldAboutMe, 105 | FieldAccountEnabled, 106 | FieldAssignedLicenses, 107 | FieldAssignedPlans, 108 | FieldBirthday, 109 | FieldBusinessPhones, 110 | FieldCity, 111 | FieldCompanyName, 112 | FieldCountry, 113 | FieldDepartment, 114 | FieldDisplayName, 115 | FieldGivenName, 116 | FieldHireDate, 117 | FieldIMAddresses, 118 | FieldInterests, 119 | FieldJobTitle, 120 | FieldMail, 121 | FieldMailboxSettings, 122 | FieldMailNickname, 123 | FieldMobilePhone, 124 | FieldMySite, 125 | FieldOfficeLocation, 126 | FieldOnPremisesImmutableID, 127 | FieldOnPremisesLastSyncDateTime, 128 | FieldOnPremisesSecurityIdentifier, 129 | FieldOnPremisesSyncEnabled, 130 | FieldPasswordPolicies, 131 | FieldPasswordProfile, 132 | FieldPastProjects, 133 | FieldPostalCode, 134 | FieldPreferredLanguage, 135 | FieldPreferredName, 136 | FieldProvisionedPlans, 137 | FieldProxyAddresses, 138 | FieldResponsibilities, 139 | FieldSchools, 140 | FieldSkills, 141 | FieldState, 142 | FieldStreetAddress, 143 | FieldSurname, 144 | FieldUsageLocation, 145 | FieldUserPrincipalName, 146 | } 147 | // UserDefaultFields specifies the Microsoft-specified default fields available for selection 148 | // in API calls. 149 | UserDefaultFields = []Field{ 150 | FieldAccountEnabled, 151 | FieldBusinessPhones, 152 | FieldDisplayName, 153 | FieldGivenName, 154 | FieldID, 155 | FieldJobTitle, 156 | FieldMail, 157 | FieldMobilePhone, 158 | FieldOfficeLocation, 159 | FieldPreferredLanguage, 160 | FieldSurname, 161 | FieldUserPrincipalName, 162 | } 163 | ) 164 | -------------------------------------------------------------------------------- /users/licenses.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | // AssignedLicense represents a license assigned to a user 4 | type AssignedLicense struct { 5 | DisabledPlans []string `json:"disabledPlans"` 6 | SKUID string `json:"skuId"` 7 | } 8 | -------------------------------------------------------------------------------- /users/locale.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | // LocaleInfo contains information about the locale, including the preferred language and 4 | // country/region, of the signed-in user. 5 | type LocaleInfo struct { 6 | DisplayName string `json:"displayName"` 7 | Locale string `json:"locale"` 8 | } 9 | -------------------------------------------------------------------------------- /users/mailbox.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | // AutomaticRepliesSetting configuration settings to automatically notify the sender of an 4 | // incoming email with a message from the signed-in user. For example, an automatic reply to 5 | // notify that the signed-in user is unavailable to respond to emails. 6 | type AutomaticRepliesSetting struct { 7 | ExternalAudience string `json:"externalAudience"` 8 | ExternalReplyMessage string `json:"externalReplyMessage"` 9 | InternalReplyMessage string `json:"InternalReplyMessage"` 10 | ScheduledEndDateTime string `json:"scheduledEndDateTime"` 11 | ScheduledStartDateTime string `json:"scheduledStartDateTime"` 12 | Status string `json:"status"` 13 | } 14 | 15 | // MailboxSettings Settings for the primary mailbox of the signed-in user. 16 | type MailboxSettings struct { 17 | AutomaticRepliesSetting AutomaticRepliesSetting `json:"automaticRepliesSetting"` 18 | Language LocaleInfo `json:"localeInfo"` 19 | TimeZone string `json:"timeZone"` 20 | } 21 | -------------------------------------------------------------------------------- /users/password.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | // PasswordProfile Contains the password profile associated with a user. 4 | type PasswordProfile struct { 5 | ForceChangePasswordNextSignIn bool `json:"forceChangePasswordNextSignIn"` 6 | Password string `json:"password"` 7 | } 8 | -------------------------------------------------------------------------------- /users/service.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | 8 | "github.com/mhoc/msgoraph/client" 9 | "github.com/mhoc/msgoraph/internal" 10 | ) 11 | 12 | // CreateUserRequest is all the available args you can set when creating a user. 13 | type CreateUserRequest struct { 14 | AccountEnabled bool `json:"accountEnabled"` 15 | DisplayName string `json:"displayName"` 16 | OnPremisesImmutableID string `json:"onPremisesImmutableId"` 17 | MailNickname string `json:"mailNickname"` 18 | PasswordProfile PasswordProfile `json:"passwordProfile"` 19 | UserPrincipalName string `json:"userPrincipalName"` 20 | } 21 | 22 | // GetUserResponse is the response to expect on a GetUser Request. 23 | type GetUserResponse struct { 24 | Context string `json:"@odata.context"` 25 | User 26 | } 27 | 28 | // ListUsersResponse is the Response from the list users graph api endpoint 29 | type ListUsersResponse struct { 30 | Context string `json:"@odata.context"` 31 | NextPage string `json:"@odata.nextLink"` 32 | Value []User `json:"value"` 33 | } 34 | 35 | // ServiceContext represents a namespace under which all of the operations against user-namespaced 36 | // resources are accessed. 37 | type ServiceContext struct { 38 | client client.Client 39 | } 40 | 41 | // Service creates a new users.ServiceContext with the given authentication credentials. 42 | func Service(client client.Client) *ServiceContext { 43 | return &ServiceContext{client: client} 44 | } 45 | 46 | // UpdateUserRequest contains the request body to update a user. 47 | type UpdateUserRequest struct { 48 | AboutMe string `json:"aboutMe"` 49 | AccountEnabled string `json:"accountEnabled"` 50 | AssignedLicenses []AssignedLicense `json:"assignedLicenses"` 51 | Birthday string `json:"birthday"` 52 | City string `json:"city"` 53 | Country string `json:"country"` 54 | Department string `json:"department"` 55 | DisplayName string `json:"displayName"` 56 | GivenName string `json:"givenName"` 57 | HireDate string `json:"hireDate"` 58 | Interests []string `json:"interests"` 59 | JobTitle string `json:"jobTitle"` 60 | MailNickname string `json:"mailNickname"` 61 | MobilePhone string `json:"mobilePhone"` 62 | MySite string `json:"mySite"` 63 | OfficeLocation string `json:"officeLocation"` 64 | OnPremisesImmutableID string `json:"onPremisesImmutableId"` 65 | PasswordPolicies string `json:"passwordPolicies"` 66 | PasswordProfile PasswordProfile `json:"passwordProfile"` 67 | PastProjects []string `json:"pastProjects"` 68 | PostalCode string `json:"postalCode"` 69 | PreferredLanguage string `json:"preferredLanguage"` 70 | PreferredName string `json:"preferredName"` 71 | Responsibilities []string `json:"responsibilities"` 72 | Schools []string `json:"schools"` 73 | Skills []string `json:"skills"` 74 | State string `json:"state"` 75 | StreetAddress string `json:"streetAddress"` 76 | Surname string `json:"surname"` 77 | UsageLocation string `json:"usageLocation"` 78 | UserPrincipalName string `json:"userPrincipalName"` 79 | UserType string `json:"userType"` 80 | } 81 | 82 | // CreateUser creates a new user in the tenant. 83 | func (s *ServiceContext) CreateUser(createUser CreateUserRequest) (User, error) { 84 | body, err := internal.GraphRequest(s.client, "POST", "v1.0/users", nil, createUser) 85 | var data GetUserResponse 86 | err = json.Unmarshal(body, &data) 87 | if err != nil { 88 | return User{}, err 89 | } 90 | return data.User, nil 91 | } 92 | 93 | // DeleteUser deletes an existing user by id or principal name. 94 | func (s *ServiceContext) DeleteUser(userIDOrPrincipal string) error { 95 | reqURL := fmt.Sprintf("v1.0/users/%v", userIDOrPrincipal) 96 | _, err := internal.GraphRequest(s.client, "DELETE", reqURL, nil, nil) 97 | return err 98 | } 99 | 100 | // GetUser returns a single user by id or principal name, with the Microsoft default fields 101 | // provided, identical to those specified in UserDefaultFields. 102 | func (s *ServiceContext) GetUser(userIDOrPrincipal string) (User, error) { 103 | return s.GetUserWithFields(userIDOrPrincipal, UserDefaultFields) 104 | } 105 | 106 | // GetUserWithFields returns a single user by id or principal name. You need to specify a list of 107 | // fields you want to project on the user returned. You can specify UserDefaultFields or 108 | // UserAllFields, or customize it depending on what you want. 109 | func (s *ServiceContext) GetUserWithFields(userIDOrPrincipal string, projection []Field) (User, error) { 110 | if len(projection) == 0 { 111 | return User{}, fmt.Errorf("no fields provided in call to Users") 112 | } 113 | selectFields := "" 114 | for i, requestField := range projection { 115 | if i != 0 { 116 | selectFields += "," 117 | } 118 | selectFields += string(requestField) 119 | } 120 | v := url.Values{} 121 | v.Set("$select", selectFields) 122 | reqURL := fmt.Sprintf("v1.0/users/%v", userIDOrPrincipal) 123 | b, err := internal.GraphRequest(s.client, "GET", reqURL, v, nil) 124 | var data GetUserResponse 125 | err = json.Unmarshal(b, &data) 126 | if err != nil { 127 | return User{}, err 128 | } 129 | return data.User, nil 130 | } 131 | 132 | // ListUsers returns all users in the tenant, with each user projected with the Microsoft-defined 133 | // default fields identical to UserDefaultFields. 134 | func (s *ServiceContext) ListUsers() ([]User, error) { 135 | return s.ListUsersWithFields(UserDefaultFields) 136 | } 137 | 138 | // ListUsersWithFields returns the users on a tenant's azure instance. You need to specify a list of 139 | // fields you want to project on the users returned. You can specify UserDefaultFields or 140 | // UserAllFields, or customize it depending on what you want. 141 | func (s *ServiceContext) ListUsersWithFields(projection []Field) ([]User, error) { 142 | getUserPage := func(url string) ([]User, string, error) { 143 | b, err := internal.BasicGraphRequest(s.client, "GET", url) 144 | var data ListUsersResponse 145 | err = json.Unmarshal(b, &data) 146 | if err != nil { 147 | return nil, "", err 148 | } 149 | return data.Value, data.NextPage, nil 150 | } 151 | var users []User 152 | if len(projection) == 0 { 153 | return nil, fmt.Errorf("no fields provided in call to Users") 154 | } 155 | filter := "$select=" 156 | for _, requestField := range projection { 157 | filter += string(requestField) + "," 158 | } 159 | nextURL := fmt.Sprintf("https://graph.microsoft.com/v1.0/users?%v", filter) 160 | for nextURL != "" { 161 | pageUsers, next, err := getUserPage(nextURL) 162 | if err != nil { 163 | return nil, err 164 | } 165 | for _, pageUser := range pageUsers { 166 | users = append(users, pageUser) 167 | } 168 | nextURL = next 169 | } 170 | return users, nil 171 | } 172 | 173 | // UpdateUser updates a user in the microsoft graph api, by userid or principal name, which is 174 | // usually their email address. You can provide as few or many fields in the request as you'd like 175 | // to update. 176 | func (s *ServiceContext) UpdateUser(userIDOrPrincipal string, u UpdateUserRequest) error { 177 | reqURL := fmt.Sprintf("v1.0/users/%v", userIDOrPrincipal) 178 | _, err := internal.GraphRequest(s.client, "PATCH", reqURL, nil, u) 179 | return err 180 | } 181 | -------------------------------------------------------------------------------- /users/user.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "github.com/mhoc/msgoraph/common" 5 | ) 6 | 7 | // User the user resource type in the microsoft graph qpi. Interpreted from this API 8 | // documentation https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/user 9 | type User struct { 10 | ID *string `json:"id"` 11 | AboutMe *string `json:"aboutMe"` 12 | AccountEnabled *bool `json:"accountEnabled"` 13 | AssignedLicenses []AssignedLicense `json:"assignedLicenses"` 14 | AssignedPlans []common.AssignedPlan `json:"assignedPlans"` 15 | Birthday *string `json:"birthday"` 16 | BusinessPhones []string `json:"businessPhones"` 17 | CompanyName *string `json:"companyName"` 18 | Country *string `json:"country"` 19 | Department *string `json:"department"` 20 | DisplayName *string `json:"displayName"` 21 | GivenName *string `json:"givenName"` 22 | HireDate *string `json:"hireDate"` 23 | IMAddresses []string `json:"imAddresses"` 24 | Interests []string `json:"interests"` 25 | JobTitle *string `json:"jobTitle"` 26 | Mail *string `json:"mail"` 27 | MailboxSettings *MailboxSettings `json:"mailboxSettings"` 28 | MailNickname *string `json:"mailNickname"` 29 | MobilePhone *string `json:"mobilePhone"` 30 | MySite *string `json:"mySite"` 31 | OfficeLocation *string `json:"officeLocation"` 32 | OnPremisesImmutableID *string `json:"onPremisesImmutableId"` 33 | OnPremisesLastSyncDateTime *string `json:"onPremisesLastSyncDateTime"` 34 | OnPremisesSecurityIdentifier *string `json:"onPremisesSecurityIdentifier"` 35 | OnPremisesSyncEnabled *bool `json:"onPremisesSyncEnabled"` 36 | PasswordPolicies *string `json:"passwordPolicies"` 37 | PasswordProfile *PasswordProfile `json:"passwordProfile"` 38 | PastProjects []string `json:"pastProjects"` 39 | PostalCode *string `json:"postalCode"` 40 | PreferredLanguage *string `json:"preferredLanguage"` 41 | PreferredName *string `json:"preferredName"` 42 | ProvisionedPlans []common.ProvisionedPlan `json:"provisionedPlans"` 43 | ProxyAddresses []string `json:"proxyAddresses"` 44 | Responsibilities *string `json:"responsibilities"` 45 | Schools *string `json:"schools"` 46 | Skills *string `json:"skills"` 47 | State *string `json:"state"` 48 | StreetAddress *string `json:"streetAddress"` 49 | Surname *string `json:"surname"` 50 | UsageLocation *string `json:"string"` 51 | UserPrincipalName *string `json:"userPrincipalName"` 52 | UserType *string `json:"userType"` 53 | } 54 | --------------------------------------------------------------------------------