| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package oauth2 |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "net/http" |
| "net/url" |
| "strconv" |
| "strings" |
| ) |
| |
| // DeviceCodeProvider holds the information needed to easily get a |
| // device code locally. |
| type LocalDeviceCodeProvider struct { |
| options LocalDeviceCodeProviderOptions |
| oidcWellKnownEndpoints OIDCWellKnownEndpoints |
| transport HTTPAuthTransport |
| } |
| |
| type DeviceCodeRequest struct { |
| ClientID string |
| Scopes []string |
| Audience string |
| } |
| |
| // DeviceCodeResult holds the device code gotten from the device code URL. |
| type DeviceCodeResult struct { |
| DeviceCode string `json:"device_code"` |
| UserCode string `json:"user_code"` |
| VerificationURI string `json:"verification_uri"` |
| VerificationURIComplete string `json:"verification_uri_complete"` |
| ExpiresIn int `json:"expires_in"` |
| Interval int `json:"interval"` |
| } |
| |
| type LocalDeviceCodeProviderOptions struct { |
| ClientID string |
| } |
| |
| // NewLocalDeviceCodeProvider allows for the easy setup of LocalDeviceCodeProvider |
| func NewLocalDeviceCodeProvider( |
| options LocalDeviceCodeProviderOptions, |
| oidcWellKnownEndpoints OIDCWellKnownEndpoints, |
| authTransport HTTPAuthTransport) *LocalDeviceCodeProvider { |
| return &LocalDeviceCodeProvider{ |
| options, |
| oidcWellKnownEndpoints, |
| authTransport, |
| } |
| } |
| |
| // GetCode obtains a new device code. Additional scopes |
| // beyond openid and email can be sent by passing in arguments for |
| // <additionalScopes>. |
| func (cp *LocalDeviceCodeProvider) GetCode(audience string, additionalScopes ...string) (*DeviceCodeResult, error) { |
| request, err := cp.newDeviceCodeRequest(&DeviceCodeRequest{ |
| ClientID: cp.options.ClientID, |
| Scopes: append([]string{"openid", "email"}, additionalScopes...), |
| Audience: audience, |
| }) |
| if err != nil { |
| return nil, err |
| } |
| |
| response, err := cp.transport.Do(request) |
| if err != nil { |
| return nil, err |
| } |
| |
| dcr, err := cp.handleDeviceCodeResponse(response) |
| if err != nil { |
| return nil, err |
| } |
| |
| return dcr, nil |
| } |
| |
| // newDeviceCodeRequest builds a new DeviceCodeRequest wrapped in an |
| // http.Request |
| func (cp *LocalDeviceCodeProvider) newDeviceCodeRequest( |
| req *DeviceCodeRequest) (*http.Request, error) { |
| uv := url.Values{} |
| uv.Set("client_id", req.ClientID) |
| uv.Set("scope", strings.Join(req.Scopes, " ")) |
| uv.Set("audience", req.Audience) |
| euv := uv.Encode() |
| |
| request, err := http.NewRequest("POST", |
| cp.oidcWellKnownEndpoints.DeviceAuthorizationEndpoint, |
| strings.NewReader(euv), |
| ) |
| if err != nil { |
| return nil, err |
| } |
| |
| request.Header.Add("Content-Type", "application/x-www-form-urlencoded") |
| request.Header.Add("Content-Length", strconv.Itoa(len(euv))) |
| |
| return request, nil |
| } |
| |
| func (cp *LocalDeviceCodeProvider) handleDeviceCodeResponse(resp *http.Response) (*DeviceCodeResult, error) { |
| if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
| return nil, fmt.Errorf("a non-success status code was received: %d", resp.StatusCode) |
| } |
| |
| defer resp.Body.Close() |
| |
| dcr := DeviceCodeResult{} |
| err := json.NewDecoder(resp.Body).Decode(&dcr) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &dcr, nil |
| } |