Merge branch 'master' into feature/graph-table-time-format
commit
9b9be3cda1
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 1.4.2.2
|
||||
current_version = 1.4.2.3
|
||||
files = README.md server/swagger.json
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.(?P<release>\d+)
|
||||
serialize = {major}.{minor}.{patch}.{release}
|
||||
|
|
1189
CHANGELOG.md
1189
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,7 @@ We really like to receive feature requests, as it helps us prioritize our work.
|
|||
|
||||
Contributing to the source code
|
||||
-------------------------------
|
||||
Chronograf is built using Go for its API backend and serving the front-end assets. The front-end visualization is built with React and uses Yarn for package management. The assumption is that all your Go development are done in `$GOPATH/src`. `GOPATH` can be any directory under which Chronograf and all its dependencies will be cloned. For full details on the project structure, follow along below.
|
||||
Chronograf is built using Go for its API backend and serving the front-end assets, and uses Dep for dependency management. The front-end visualization is built with React (JavaScript) and uses Yarn for dependency management. The assumption is that all your Go development are done in `$GOPATH/src`. `GOPATH` can be any directory under which Chronograf and all its dependencies will be cloned. For full details on the project structure, follow along below.
|
||||
|
||||
Submitting a pull request
|
||||
-------------------------
|
||||
|
@ -43,9 +43,13 @@ Signing the CLA
|
|||
If you are going to be contributing back to Chronograf please take a second to sign our CLA, which can be found
|
||||
[on our website](https://influxdata.com/community/cla/).
|
||||
|
||||
Installing Yarn
|
||||
Installing & Using Yarn
|
||||
--------------
|
||||
You'll need to install Yarn to manage the JavaScript modules that the front-end uses. This varies depending on what platform you're developing on, but you should be able to find an installer on [the Yarn installation page](https://yarnpkg.com/en/docs/install).
|
||||
You'll need to install Yarn to manage the frontend (JavaScript) dependencies.
|
||||
|
||||
* [Install Yarn](https://yarnpkg.com/en/docs/install)
|
||||
|
||||
To add a dependency via Yarn, for example, run `yarn add <dependency>` from within the `/chronograf/ui` directory.
|
||||
|
||||
Installing Go
|
||||
-------------
|
||||
|
@ -62,13 +66,13 @@ running the following:
|
|||
gvm use go1.7.5 --default
|
||||
```
|
||||
|
||||
Installing GDM
|
||||
Installing & Using Dep
|
||||
--------------
|
||||
Chronograf uses [gdm](https://github.com/sparrc/gdm) to manage dependencies. Install it by running the following:
|
||||
You'll need to install Dep to manage the backend (Go) dependencies.
|
||||
|
||||
```bash
|
||||
go get github.com/sparrc/gdm
|
||||
```
|
||||
* [Install Dep](https://github.com/golang/dep)
|
||||
|
||||
To add a dependency via Dep, for example, run `dep ensure -add <dependency>` from within the `/chronograf` directory. _Note that as of this writing, `dep ensure` will modify many extraneous vendor files, so you'll need to run `dep prune` to clean this up before committing your changes. Apparently, the next version of `dep` will take care of this step for you._
|
||||
|
||||
Revision Control Systems
|
||||
------------------------
|
||||
|
|
|
@ -39,34 +39,7 @@
|
|||
|
||||
[[projects]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"gogoproto",
|
||||
"jsonpb",
|
||||
"plugin/compare",
|
||||
"plugin/defaultcheck",
|
||||
"plugin/description",
|
||||
"plugin/embedcheck",
|
||||
"plugin/enumstringer",
|
||||
"plugin/equal",
|
||||
"plugin/face",
|
||||
"plugin/gostring",
|
||||
"plugin/marshalto",
|
||||
"plugin/oneofcheck",
|
||||
"plugin/populate",
|
||||
"plugin/size",
|
||||
"plugin/stringer",
|
||||
"plugin/testgen",
|
||||
"plugin/union",
|
||||
"plugin/unmarshal",
|
||||
"proto",
|
||||
"protoc-gen-gogo",
|
||||
"protoc-gen-gogo/descriptor",
|
||||
"protoc-gen-gogo/generator",
|
||||
"protoc-gen-gogo/grpc",
|
||||
"protoc-gen-gogo/plugin",
|
||||
"vanity",
|
||||
"vanity/command"
|
||||
]
|
||||
packages = ["gogoproto","jsonpb","plugin/compare","plugin/defaultcheck","plugin/description","plugin/embedcheck","plugin/enumstringer","plugin/equal","plugin/face","plugin/gostring","plugin/marshalto","plugin/oneofcheck","plugin/populate","plugin/size","plugin/stringer","plugin/testgen","plugin/union","plugin/unmarshal","proto","protoc-gen-gogo","protoc-gen-gogo/descriptor","protoc-gen-gogo/generator","protoc-gen-gogo/grpc","protoc-gen-gogo/plugin","vanity","vanity/command"]
|
||||
revision = "6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b"
|
||||
|
||||
[[projects]]
|
||||
|
@ -77,13 +50,7 @@
|
|||
|
||||
[[projects]]
|
||||
name = "github.com/google/go-cmp"
|
||||
packages = [
|
||||
"cmp",
|
||||
"cmp/cmpopts",
|
||||
"cmp/internal/diff",
|
||||
"cmp/internal/function",
|
||||
"cmp/internal/value"
|
||||
]
|
||||
packages = ["cmp","cmp/cmpopts","cmp/internal/diff","cmp/internal/function","cmp/internal/value"]
|
||||
revision = "8099a9787ce5dc5984ed879a3bda47dc730a8e97"
|
||||
version = "v0.1.0"
|
||||
|
||||
|
@ -100,28 +67,13 @@
|
|||
|
||||
[[projects]]
|
||||
name = "github.com/influxdata/influxdb"
|
||||
packages = [
|
||||
"influxql",
|
||||
"influxql/internal",
|
||||
"influxql/neldermead",
|
||||
"models",
|
||||
"pkg/escape"
|
||||
]
|
||||
packages = ["influxql","influxql/internal","influxql/neldermead","models","pkg/escape"]
|
||||
revision = "cd9363b52cac452113b95554d98a6be51beda24e"
|
||||
version = "v1.1.5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/influxdata/kapacitor"
|
||||
packages = [
|
||||
"client/v1",
|
||||
"pipeline",
|
||||
"pipeline/tick",
|
||||
"services/k8s/client",
|
||||
"tick",
|
||||
"tick/ast",
|
||||
"tick/stateful",
|
||||
"udf/agent"
|
||||
]
|
||||
packages = ["client/v1","pipeline","pipeline/tick","services/k8s/client","tick","tick/ast","tick/stateful","udf/agent"]
|
||||
revision = "6de30070b39afde111fea5e041281126fe8aae31"
|
||||
|
||||
[[projects]]
|
||||
|
@ -163,21 +115,13 @@
|
|||
|
||||
[[projects]]
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp"
|
||||
]
|
||||
packages = ["context","context/ctxhttp"]
|
||||
revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"github",
|
||||
"heroku",
|
||||
"internal"
|
||||
]
|
||||
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
|
||||
packages = [".","github","heroku","internal"]
|
||||
revision = "2f32c3ac0fa4fb807a0fcefb0b6f2468a0d99bd0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -187,31 +131,18 @@
|
|||
|
||||
[[projects]]
|
||||
name = "google.golang.org/api"
|
||||
packages = [
|
||||
"gensupport",
|
||||
"googleapi",
|
||||
"googleapi/internal/uritemplates",
|
||||
"oauth2/v2"
|
||||
]
|
||||
packages = ["gensupport","googleapi","googleapi/internal/uritemplates","oauth2/v2"]
|
||||
revision = "bc20c61134e1d25265dd60049f5735381e79b631"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [
|
||||
"internal",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch"
|
||||
]
|
||||
packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"]
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "11df631364d11bc05c8f71af1aa735360b5a40a793d32d47d1f1d8c694a55f6f"
|
||||
inputs-digest = "a4df1b0953349e64a89581f4b83ac3a2f40e17681e19f8de3cbf828b6375a3ba"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
@ -62,7 +62,7 @@ required = ["github.com/kevinburke/go-bindata","github.com/gogo/protobuf/proto",
|
|||
|
||||
[[constraint]]
|
||||
name = "golang.org/x/oauth2"
|
||||
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
|
||||
revision = "2f32c3ac0fa4fb807a0fcefb0b6f2468a0d99bd0"
|
||||
|
||||
[[constraint]]
|
||||
name = "google.golang.org/api"
|
||||
|
|
|
@ -136,7 +136,7 @@ option.
|
|||
## Versions
|
||||
|
||||
The most recent version of Chronograf is
|
||||
[v1.4.2.2](https://www.influxdata.com/downloads/).
|
||||
[v1.4.2.3](https://www.influxdata.com/downloads/).
|
||||
|
||||
Spotted a bug or have a feature request? Please open
|
||||
[an issue](https://github.com/influxdata/chronograf/issues/new)!
|
||||
|
@ -178,7 +178,7 @@ By default, chronograf runs on port `8888`.
|
|||
To get started right away with Docker, you can pull down our latest release:
|
||||
|
||||
```sh
|
||||
docker pull chronograf:1.4.2.2
|
||||
docker pull chronograf:1.4.2.3
|
||||
```
|
||||
|
||||
### From Source
|
||||
|
|
|
@ -448,6 +448,13 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
|
|||
|
||||
}
|
||||
|
||||
// FIXME: this is merely for legacy cells and
|
||||
// should be removed as soon as possible
|
||||
cellType := c.Type
|
||||
if cellType == "" {
|
||||
cellType = "line"
|
||||
}
|
||||
|
||||
cells[i] = chronograf.DashboardCell{
|
||||
ID: c.ID,
|
||||
X: c.X,
|
||||
|
@ -456,7 +463,7 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
|
|||
H: c.H,
|
||||
Name: c.Name,
|
||||
Queries: queries,
|
||||
Type: c.Type,
|
||||
Type: cellType,
|
||||
Axes: axes,
|
||||
CellColors: colors,
|
||||
Legend: legend,
|
||||
|
|
|
@ -452,3 +452,37 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
|
|||
t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MarshalDashboard_WithEmptyCellType(t *testing.T) {
|
||||
dashboard := chronograf.Dashboard{
|
||||
ID: 1,
|
||||
Cells: []chronograf.DashboardCell{
|
||||
{
|
||||
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := chronograf.Dashboard{
|
||||
ID: 1,
|
||||
Cells: []chronograf.DashboardCell{
|
||||
{
|
||||
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
|
||||
Type: "line",
|
||||
Queries: []chronograf.DashboardQuery{},
|
||||
Axes: map[string]chronograf.Axis{},
|
||||
CellColors: []chronograf.CellColor{},
|
||||
},
|
||||
},
|
||||
Templates: []chronograf.Template{},
|
||||
}
|
||||
|
||||
var actual chronograf.Dashboard
|
||||
if buf, err := internal.MarshalDashboard(dashboard); err != nil {
|
||||
t.Fatal("Error marshaling dashboard: err", err)
|
||||
} else if err := internal.UnmarshalDashboard(buf, &actual); err != nil {
|
||||
t.Fatal("Error unmarshaling dashboard: err:", err)
|
||||
} else if !cmp.Equal(expected, actual) {
|
||||
t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
**We've moved our documentation!** Check out the latest [authentication content](https://docs.influxdata.com/chronograf/latest/administration/security-best-practices/#chronograf-with-oauth-2-0-authentication) on InfluxData's [main docs site](https://docs.influxdata.com/chronograf/latest/).
|
||||
**We've moved our documentation!** Check out the latest [authentication content](https://docs.influxdata.com/chronograf/latest/administration/managing-security/#oauth-2-0-providers-with-jwt-tokens) on InfluxData's [main docs site](https://docs.influxdata.com/chronograf/latest/).
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package oauth2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
|
@ -13,15 +14,24 @@ import (
|
|||
|
||||
var testTime = time.Date(1985, time.October, 25, 18, 0, 0, 0, time.UTC)
|
||||
|
||||
type mockCallbackResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
// setupMuxTest produces an http.Client and an httptest.Server configured to
|
||||
// use a particular http.Handler selected from a AuthMux. As this selection is
|
||||
// done during the setup process, this configuration is performed by providing
|
||||
// a function, and returning the desired handler. Cleanup is still the
|
||||
// responsibility of the test writer, so the httptest.Server's Close() method
|
||||
// should be deferred.
|
||||
func setupMuxTest(selector func(*AuthMux) http.Handler) (*http.Client, *httptest.Server, *httptest.Server) {
|
||||
func setupMuxTest(response interface{}, selector func(*AuthMux) http.Handler) (*http.Client, *httptest.Server, *httptest.Server) {
|
||||
provider := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("content-type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
body, _ := json.Marshal(response)
|
||||
|
||||
rw.Write(body)
|
||||
}))
|
||||
|
||||
now := func() time.Time {
|
||||
|
@ -63,7 +73,9 @@ func teardownMuxTest(hc *http.Client, backend *httptest.Server, provider *httpte
|
|||
func Test_AuthMux_Logout_DeletesSessionCookie(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hc, ts, prov := setupMuxTest(func(j *AuthMux) http.Handler {
|
||||
var response interface{}
|
||||
|
||||
hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler {
|
||||
return j.Logout()
|
||||
})
|
||||
defer teardownMuxTest(hc, ts, prov)
|
||||
|
@ -100,7 +112,9 @@ func Test_AuthMux_Logout_DeletesSessionCookie(t *testing.T) {
|
|||
func Test_AuthMux_Login_RedirectsToCorrectURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hc, ts, prov := setupMuxTest(func(j *AuthMux) http.Handler {
|
||||
var response interface{}
|
||||
|
||||
hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler {
|
||||
return j.Login() // Use Login handler for httptest server.
|
||||
})
|
||||
defer teardownMuxTest(hc, ts, prov)
|
||||
|
@ -126,7 +140,8 @@ func Test_AuthMux_Login_RedirectsToCorrectURL(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_AuthMux_Callback_SetsCookie(t *testing.T) {
|
||||
hc, ts, prov := setupMuxTest(func(j *AuthMux) http.Handler {
|
||||
response := mockCallbackResponse{AccessToken: "123"}
|
||||
hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler {
|
||||
return j.Callback()
|
||||
})
|
||||
defer teardownMuxTest(hc, ts, prov)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"info": {
|
||||
"title": "Chronograf",
|
||||
"description": "API endpoints for Chronograf",
|
||||
"version": "1.4.2.2"
|
||||
"version": "1.4.2.3"
|
||||
},
|
||||
"schemes": ["http"],
|
||||
"basePath": "/chronograf/v1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "chronograf-ui",
|
||||
"version": "1.4.2-2",
|
||||
"version": "1.4.2-3",
|
||||
"private": false,
|
||||
"license": "AGPL-3.0",
|
||||
"description": "",
|
||||
|
@ -62,7 +62,7 @@
|
|||
"enzyme-adapter-react-15": "^1.0.5",
|
||||
"eslint": "^3.14.1",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-loader": "1.6.1",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-jest": "^21.12.2",
|
||||
"eslint-plugin-prettier": "^2.1.2",
|
||||
"eslint-plugin-react": "6.6.0",
|
||||
|
|
|
@ -32,11 +32,14 @@ export const handleSuccess = (data, query, editQueryStatus) => {
|
|||
}
|
||||
|
||||
export const handleError = (error, query, editQueryStatus) => {
|
||||
const message = _.get(error, ['data', 'message'], error.toString())
|
||||
const message = _.get(
|
||||
error,
|
||||
['data', 'message'],
|
||||
error.message || 'Could not retrieve data'
|
||||
)
|
||||
|
||||
// 400 from chrono server = fail
|
||||
editQueryStatus(query.id, {error: message})
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
export const fetchTimeSeriesAsync = async (
|
||||
|
|
|
@ -73,7 +73,6 @@ const AJAX = async (
|
|||
return links ? generateResponseWithLinks(response, links) : response
|
||||
} catch (error) {
|
||||
const {response} = error
|
||||
|
||||
throw links ? generateResponseWithLinks(response, links) : response // eslint-disable-line no-throw-literal
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,9 @@ module.exports = {
|
|||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader',
|
||||
enforce: 'pre',
|
||||
options: {
|
||||
emitWarning: true
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
|
|
76
ui/yarn.lock
76
ui/yarn.lock
|
@ -2084,18 +2084,12 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0:
|
|||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
crossvent@1.5.0:
|
||||
crossvent@1.5.0, crossvent@^1.3.1:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/crossvent/-/crossvent-1.5.0.tgz#3779c1242699e19417f0414e61b144753a52fd6d"
|
||||
dependencies:
|
||||
custom-event "1.0.0"
|
||||
|
||||
crossvent@^1.3.1:
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/crossvent/-/crossvent-1.5.5.tgz#ad20878e4921e9be73d9d6976f8b2ecd0f71a0b1"
|
||||
dependencies:
|
||||
custom-event "^1.0.0"
|
||||
|
||||
cryptiles@2.x.x:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
|
||||
|
@ -2258,10 +2252,6 @@ custom-event@1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.0.tgz#2e4628be19dc4b214b5c02630c5971e811618062"
|
||||
|
||||
custom-event@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
|
||||
|
||||
cyclist@~0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
|
||||
|
@ -2536,20 +2526,13 @@ domutils@1.1:
|
|||
dependencies:
|
||||
domelementtype "1"
|
||||
|
||||
domutils@1.5.1:
|
||||
domutils@1.5.1, domutils@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
|
||||
dependencies:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
domutils@^1.5.1:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
||||
dependencies:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
duplexer@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
|
||||
|
@ -2796,14 +2779,15 @@ eslint-config-prettier@^2.9.0:
|
|||
dependencies:
|
||||
get-stdin "^5.0.1"
|
||||
|
||||
eslint-loader@1.6.1:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-1.6.1.tgz#96c47c812772eeb077e3a81681818e671a2cabf5"
|
||||
eslint-loader@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.0.0.tgz#d136619b5c684e36531ffc28c60a56e404608f5d"
|
||||
dependencies:
|
||||
find-cache-dir "^0.1.1"
|
||||
loader-utils "^0.2.7"
|
||||
loader-fs-cache "^1.0.0"
|
||||
loader-utils "^1.0.2"
|
||||
object-assign "^4.0.1"
|
||||
object-hash "^1.1.4"
|
||||
rimraf "^2.6.1"
|
||||
|
||||
eslint-plugin-babel@^4.1.2:
|
||||
version "4.1.2"
|
||||
|
@ -3161,14 +3145,10 @@ extract-text-webpack-plugin@^3.0.2:
|
|||
schema-utils "^0.3.0"
|
||||
webpack-sources "^1.0.1"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
extsprintf@1.3.0, extsprintf@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
|
||||
extsprintf@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
|
||||
fast-deep-equal@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
|
||||
|
@ -5054,11 +5034,18 @@ load-json-file@^2.0.0:
|
|||
pify "^2.0.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
loader-fs-cache@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc"
|
||||
dependencies:
|
||||
find-cache-dir "^0.1.1"
|
||||
mkdirp "0.5.1"
|
||||
|
||||
loader-runner@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
|
||||
|
||||
loader-utils@0.2.x, loader-utils@^0.2.12, loader-utils@^0.2.16, loader-utils@^0.2.7, loader-utils@~0.2.2:
|
||||
loader-utils@0.2.x, loader-utils@^0.2.12, loader-utils@^0.2.16, loader-utils@~0.2.2:
|
||||
version "0.2.17"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
|
||||
dependencies:
|
||||
|
@ -5471,7 +5458,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
|
|||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@0.0.8:
|
||||
minimist@0.0.8, minimist@~0.0.1:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
|
@ -5479,10 +5466,6 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
|
|||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
minimist@~0.0.1:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
|
||||
mississippi@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f"
|
||||
|
@ -5512,7 +5495,7 @@ mixin-object@^2.0.1:
|
|||
for-in "^0.1.3"
|
||||
is-extendable "^0.1.1"
|
||||
|
||||
mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
|
@ -6918,19 +6901,12 @@ react-dom@^15.0.2:
|
|||
object-assign "^4.1.0"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react-draggable@^2.1.1:
|
||||
react-draggable@^2.1.1, "react-draggable@^2.2.6 || ^3.0.3":
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-2.2.6.tgz#3a806e10f2da6babfea4136be6510e89b0d76901"
|
||||
dependencies:
|
||||
classnames "^2.2.5"
|
||||
|
||||
"react-draggable@^2.2.6 || ^3.0.3":
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.0.5.tgz#c031e0ed4313531f9409d6cd84c8ebcec0ddfe2d"
|
||||
dependencies:
|
||||
classnames "^2.2.5"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-grid-layout@^0.13.9:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-0.13.9.tgz#5ba29cdc0e6422362f46d9a49047ebb24e0e293e"
|
||||
|
@ -7965,11 +7941,7 @@ static-extend@^0.1.1:
|
|||
define-property "^0.2.5"
|
||||
object-copy "^0.1.0"
|
||||
|
||||
"statuses@>= 1.3.1 < 2":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
|
||||
|
||||
statuses@~1.3.1:
|
||||
"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
|
||||
|
||||
|
@ -8875,14 +8847,10 @@ window-size@0.1.0:
|
|||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
|
||||
|
||||
wordwrap@0.0.2:
|
||||
wordwrap@0.0.2, wordwrap@~0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
||||
|
||||
wordwrap@~0.0.2:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
|
||||
|
||||
wordwrap@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2009 The oauth2 Authors. All rights reserved.
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
|
|
@ -11,6 +11,9 @@ oauth2 package contains a client implementation for OAuth 2.0 spec.
|
|||
go get golang.org/x/oauth2
|
||||
~~~~
|
||||
|
||||
Or you can manually git clone the repository to
|
||||
`$(go env GOPATH)/src/golang.org/x/oauth2`.
|
||||
|
||||
See godoc for further documentation and examples.
|
||||
|
||||
* [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2)
|
||||
|
@ -19,11 +22,11 @@ See godoc for further documentation and examples.
|
|||
|
||||
## App Engine
|
||||
|
||||
In change 96e89be (March 2015) we removed the `oauth2.Context2` type in favor
|
||||
In change 96e89be (March 2015), we removed the `oauth2.Context2` type in favor
|
||||
of the [`context.Context`](https://golang.org/x/net/context#Context) type from
|
||||
the `golang.org/x/net/context` package
|
||||
|
||||
This means its no longer possible to use the "Classic App Engine"
|
||||
This means it's no longer possible to use the "Classic App Engine"
|
||||
`appengine.Context` type with the `oauth2` package. (You're using
|
||||
Classic App Engine if you import the package `"appengine"`.)
|
||||
|
||||
|
@ -39,27 +42,36 @@ If you don't want to update your entire app to use the new App Engine packages,
|
|||
you may use both sets of packages in parallel, using only the new packages
|
||||
with the `oauth2` package.
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
newappengine "google.golang.org/appengine"
|
||||
newurlfetch "google.golang.org/appengine/urlfetch"
|
||||
```go
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
newappengine "google.golang.org/appengine"
|
||||
newurlfetch "google.golang.org/appengine/urlfetch"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
"appengine"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
var c appengine.Context = appengine.NewContext(r)
|
||||
c.Infof("Logging a message with the old package")
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
var c appengine.Context = appengine.NewContext(r)
|
||||
c.Infof("Logging a message with the old package")
|
||||
|
||||
var ctx context.Context = newappengine.NewContext(r)
|
||||
client := &http.Client{
|
||||
Transport: &oauth2.Transport{
|
||||
Source: google.AppEngineTokenSource(ctx, "scope"),
|
||||
Base: &newurlfetch.Transport{Context: ctx},
|
||||
},
|
||||
}
|
||||
client.Get("...")
|
||||
var ctx context.Context = newappengine.NewContext(r)
|
||||
client := &http.Client{
|
||||
Transport: &oauth2.Transport{
|
||||
Source: google.AppEngineTokenSource(ctx, "scope"),
|
||||
Base: &newurlfetch.Transport{Context: ctx},
|
||||
},
|
||||
}
|
||||
client.Get("...")
|
||||
}
|
||||
```
|
||||
|
||||
## Report Issues / Send Patches
|
||||
|
||||
This repository uses Gerrit for code changes. To learn how to submit changes to
|
||||
this repository, see https://golang.org/doc/contribute.html.
|
||||
|
||||
The main issue tracker for the oauth2 repository is located at
|
||||
https://github.com/golang/oauth2/issues.
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
// App Engine hooks.
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/internal"
|
||||
"google.golang.org/appengine/urlfetch"
|
||||
)
|
||||
|
||||
func init() {
|
||||
internal.RegisterContextClientFunc(contextClientAppEngine)
|
||||
}
|
||||
|
||||
func contextClientAppEngine(ctx context.Context) (*http.Client, error) {
|
||||
return urlfetch.Client(ctx), nil
|
||||
}
|
|
@ -8,6 +8,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
@ -45,3 +47,43 @@ func ExampleConfig() {
|
|||
client := conf.Client(ctx, tok)
|
||||
client.Get("...")
|
||||
}
|
||||
|
||||
func ExampleConfig_customHTTP() {
|
||||
ctx := context.Background()
|
||||
|
||||
conf := &oauth2.Config{
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
Scopes: []string{"SCOPE1", "SCOPE2"},
|
||||
Endpoint: oauth2.Endpoint{
|
||||
TokenURL: "https://provider.com/o/oauth2/token",
|
||||
AuthURL: "https://provider.com/o/oauth2/auth",
|
||||
},
|
||||
}
|
||||
|
||||
// Redirect user to consent page to ask for permission
|
||||
// for the scopes specified above.
|
||||
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
|
||||
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
||||
|
||||
// Use the authorization code that is pushed to the redirect
|
||||
// URL. Exchange will do the handshake to retrieve the
|
||||
// initial access token. The HTTP Client returned by
|
||||
// conf.Client will refresh the token as necessary.
|
||||
var code string
|
||||
if _, err := fmt.Scan(&code); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Use the custom HTTP client when requesting a token.
|
||||
httpClient := &http.Client{Timeout: 2 * time.Second}
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
|
||||
|
||||
tok, err := conf.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
client := conf.Client(ctx, tok)
|
||||
_ = client
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package internal
|
||||
|
||||
import "google.golang.org/appengine/urlfetch"
|
||||
|
||||
func init() {
|
||||
appengineClientHook = urlfetch.Client
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
|
@ -2,18 +2,14 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseKey converts the binary contents of a private key file
|
||||
|
@ -39,38 +35,3 @@ func ParseKey(key []byte) (*rsa.PrivateKey, error) {
|
|||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func ParseINI(ini io.Reader) (map[string]map[string]string, error) {
|
||||
result := map[string]map[string]string{
|
||||
"": map[string]string{}, // root section
|
||||
}
|
||||
scanner := bufio.NewScanner(ini)
|
||||
currentSection := ""
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, ";") {
|
||||
// comment.
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||
currentSection = strings.TrimSpace(line[1 : len(line)-1])
|
||||
result[currentSection] = map[string]string{}
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) == 2 && parts[0] != "" {
|
||||
result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error scanning ini: %v", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CondVal(v string) []string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{v}
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseINI(t *testing.T) {
|
||||
tests := []struct {
|
||||
ini string
|
||||
want map[string]map[string]string
|
||||
}{
|
||||
{
|
||||
`root = toor
|
||||
[foo]
|
||||
bar = hop
|
||||
ini = nin
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{"root": "toor"},
|
||||
"foo": map[string]string{"bar": "hop", "ini": "nin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
`[empty]
|
||||
[section]
|
||||
empty=
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{},
|
||||
"empty": map[string]string{},
|
||||
"section": map[string]string{"empty": ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
`ignore
|
||||
[invalid
|
||||
=stuff
|
||||
;comment=true
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
result, err := ParseINI(strings.NewReader(tt.ini))
|
||||
if err != nil {
|
||||
t.Errorf("ParseINI(%q) error %v, want: no error", tt.ini, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(result, tt.want) {
|
||||
t.Errorf("ParseINI(%q) = %#v, want: %#v", tt.ini, result, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,11 +2,11 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -18,9 +18,10 @@ import (
|
|||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
)
|
||||
|
||||
// Token represents the crendentials used to authorize
|
||||
// Token represents the credentials used to authorize
|
||||
// the requests to access protected resources on the OAuth 2.0
|
||||
// provider's backend.
|
||||
//
|
||||
|
@ -91,6 +92,7 @@ func (e *expirationTime) UnmarshalJSON(b []byte) error {
|
|||
|
||||
var brokenAuthHeaderProviders = []string{
|
||||
"https://accounts.google.com/",
|
||||
"https://api.codeswholesale.com/oauth/token",
|
||||
"https://api.dropbox.com/",
|
||||
"https://api.dropboxapi.com/",
|
||||
"https://api.instagram.com/",
|
||||
|
@ -101,8 +103,11 @@ var brokenAuthHeaderProviders = []string{
|
|||
"https://api.twitch.tv/",
|
||||
"https://app.box.com/",
|
||||
"https://connect.stripe.com/",
|
||||
"https://login.mailchimp.com/",
|
||||
"https://login.microsoftonline.com/",
|
||||
"https://login.salesforce.com/",
|
||||
"https://login.windows.net",
|
||||
"https://login.live.com/",
|
||||
"https://oauth.sandbox.trainingpeaks.com/",
|
||||
"https://oauth.trainingpeaks.com/",
|
||||
"https://oauth.vk.com/",
|
||||
|
@ -117,6 +122,19 @@ var brokenAuthHeaderProviders = []string{
|
|||
"https://www.strava.com/oauth/",
|
||||
"https://www.wunderlist.com/oauth/",
|
||||
"https://api.patreon.com/",
|
||||
"https://sandbox.codeswholesale.com/oauth/token",
|
||||
"https://api.sipgate.com/v1/authorization/oauth",
|
||||
"https://api.medium.com/v1/tokens",
|
||||
"https://log.finalsurge.com/oauth/token",
|
||||
}
|
||||
|
||||
// brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints.
|
||||
var brokenAuthHeaderDomains = []string{
|
||||
".auth0.com",
|
||||
".force.com",
|
||||
".myshopify.com",
|
||||
".okta.com",
|
||||
".oktapreview.com",
|
||||
}
|
||||
|
||||
func RegisterBrokenAuthHeaderProvider(tokenURL string) {
|
||||
|
@ -139,6 +157,14 @@ func providerAuthHeaderWorks(tokenURL string) bool {
|
|||
}
|
||||
}
|
||||
|
||||
if u, err := url.Parse(tokenURL); err == nil {
|
||||
for _, s := range brokenAuthHeaderDomains {
|
||||
if strings.HasSuffix(u.Host, s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assume the provider implements the spec properly
|
||||
// otherwise. We can add more exceptions as they're
|
||||
// discovered. We will _not_ be adding configurable hooks
|
||||
|
@ -147,14 +173,14 @@ func providerAuthHeaderWorks(tokenURL string) bool {
|
|||
}
|
||||
|
||||
func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) {
|
||||
hc, err := ContextClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Set("client_id", clientID)
|
||||
bustedAuth := !providerAuthHeaderWorks(tokenURL)
|
||||
if bustedAuth && clientSecret != "" {
|
||||
v.Set("client_secret", clientSecret)
|
||||
if bustedAuth {
|
||||
if clientID != "" {
|
||||
v.Set("client_id", clientID)
|
||||
}
|
||||
if clientSecret != "" {
|
||||
v.Set("client_secret", clientSecret)
|
||||
}
|
||||
}
|
||||
req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
|
@ -162,9 +188,9 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
|
|||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if !bustedAuth {
|
||||
req.SetBasicAuth(clientID, clientSecret)
|
||||
req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
|
||||
}
|
||||
r, err := hc.Do(req)
|
||||
r, err := ctxhttp.Do(ctx, ContextClient(ctx), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -174,7 +200,10 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
|
|||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||
}
|
||||
if code := r.StatusCode; code < 200 || code > 299 {
|
||||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
|
||||
return nil, &RetrieveError{
|
||||
Response: r,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
|
||||
var token *Token
|
||||
|
@ -221,5 +250,17 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
|
|||
if token.RefreshToken == "" {
|
||||
token.RefreshToken = v.Get("refresh_token")
|
||||
}
|
||||
if token.AccessToken == "" {
|
||||
return token, errors.New("oauth2: server response missing access_token")
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
type RetrieveError struct {
|
||||
Response *http.Response
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (r *RetrieveError) Error() string {
|
||||
return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
|
||||
}
|
||||
|
|
|
@ -2,12 +2,17 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestRegisterBrokenAuthHeaderProvider(t *testing.T) {
|
||||
|
@ -18,6 +23,28 @@ func TestRegisterBrokenAuthHeaderProvider(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRetrieveTokenBustedNoSecret(t *testing.T) {
|
||||
const clientID = "client-id"
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.FormValue("client_id"), clientID; got != want {
|
||||
t.Errorf("client_id = %q; want %q", got, want)
|
||||
}
|
||||
if got, want := r.FormValue("client_secret"), ""; got != want {
|
||||
t.Errorf("client_secret = %q; want empty", got)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
io.WriteString(w, `{"access_token": "ACCESS_TOKEN", "token_type": "bearer"}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
RegisterBrokenAuthHeaderProvider(ts.URL)
|
||||
_, err := RetrieveToken(context.Background(), clientID, "", ts.URL, url.Values{})
|
||||
if err != nil {
|
||||
t.Errorf("RetrieveToken = %v; want no error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_providerAuthHeaderWorks(t *testing.T) {
|
||||
for _, p := range brokenAuthHeaderProviders {
|
||||
if providerAuthHeaderWorks(p) {
|
||||
|
@ -33,3 +60,53 @@ func Test_providerAuthHeaderWorks(t *testing.T) {
|
|||
t.Errorf("got %q as unbroken; want broken", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderAuthHeaderWorksDomain(t *testing.T) {
|
||||
tests := []struct {
|
||||
tokenURL string
|
||||
wantWorks bool
|
||||
}{
|
||||
{"https://dev-12345.okta.com/token-url", false},
|
||||
{"https://dev-12345.oktapreview.com/token-url", false},
|
||||
{"https://dev-12345.okta.org/token-url", true},
|
||||
{"https://foo.bar.force.com/token-url", false},
|
||||
{"https://foo.force.com/token-url", false},
|
||||
{"https://force.com/token-url", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := providerAuthHeaderWorks(test.tokenURL)
|
||||
if got != test.wantWorks {
|
||||
t.Errorf("providerAuthHeaderWorks(%q) = %v; want %v", test.tokenURL, got, test.wantWorks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrieveTokenWithContexts(t *testing.T) {
|
||||
const clientID = "client-id"
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
io.WriteString(w, `{"access_token": "ACCESS_TOKEN", "token_type": "bearer"}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
_, err := RetrieveToken(context.Background(), clientID, "", ts.URL, url.Values{})
|
||||
if err != nil {
|
||||
t.Errorf("RetrieveToken (with background context) = %v; want no error", err)
|
||||
}
|
||||
|
||||
retrieved := make(chan struct{})
|
||||
cancellingts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
<-retrieved
|
||||
}))
|
||||
defer cancellingts.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
_, err = RetrieveToken(ctx, clientID, "", cancellingts.URL, url.Values{})
|
||||
close(retrieved)
|
||||
if err == nil {
|
||||
t.Errorf("RetrieveToken (with cancelled context) = nil; want error")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
|
@ -20,50 +19,16 @@ var HTTPClient ContextKey
|
|||
// because nobody else can create a ContextKey, being unexported.
|
||||
type ContextKey struct{}
|
||||
|
||||
// ContextClientFunc is a func which tries to return an *http.Client
|
||||
// given a Context value. If it returns an error, the search stops
|
||||
// with that error. If it returns (nil, nil), the search continues
|
||||
// down the list of registered funcs.
|
||||
type ContextClientFunc func(context.Context) (*http.Client, error)
|
||||
var appengineClientHook func(context.Context) *http.Client
|
||||
|
||||
var contextClientFuncs []ContextClientFunc
|
||||
|
||||
func RegisterContextClientFunc(fn ContextClientFunc) {
|
||||
contextClientFuncs = append(contextClientFuncs, fn)
|
||||
}
|
||||
|
||||
func ContextClient(ctx context.Context) (*http.Client, error) {
|
||||
func ContextClient(ctx context.Context) *http.Client {
|
||||
if ctx != nil {
|
||||
if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok {
|
||||
return hc, nil
|
||||
return hc
|
||||
}
|
||||
}
|
||||
for _, fn := range contextClientFuncs {
|
||||
c, err := fn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c != nil {
|
||||
return c, nil
|
||||
}
|
||||
if appengineClientHook != nil {
|
||||
return appengineClientHook(ctx)
|
||||
}
|
||||
return http.DefaultClient, nil
|
||||
}
|
||||
|
||||
func ContextTransport(ctx context.Context) http.RoundTripper {
|
||||
hc, err := ContextClient(ctx)
|
||||
// This is a rare error case (somebody using nil on App Engine).
|
||||
if err != nil {
|
||||
return ErrorTransport{err}
|
||||
}
|
||||
return hc.Transport
|
||||
}
|
||||
|
||||
// ErrorTransport returns the specified error on RoundTrip.
|
||||
// This RoundTripper should be used in rare error cases where
|
||||
// error handling can be postponed to response handling time.
|
||||
type ErrorTransport struct{ Err error }
|
||||
|
||||
func (t ErrorTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
||||
return nil, t.Err
|
||||
return http.DefaultClient
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestContextClient(t *testing.T) {
|
||||
rc := &http.Client{}
|
||||
RegisterContextClientFunc(func(context.Context) (*http.Client, error) {
|
||||
return rc, nil
|
||||
})
|
||||
|
||||
c := &http.Client{}
|
||||
ctx := context.WithValue(context.Background(), HTTPClient, c)
|
||||
|
||||
hc, err := ContextClient(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("want valid client; got err = %v", err)
|
||||
}
|
||||
if hc != c {
|
||||
t.Fatalf("want context client = %p; got = %p", c, hc)
|
||||
}
|
||||
|
||||
hc, err = ContextClient(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("want valid client; got err = %v", err)
|
||||
}
|
||||
if hc != rc {
|
||||
t.Fatalf("want registered client = %p; got = %p", c, hc)
|
||||
}
|
||||
}
|
|
@ -117,7 +117,7 @@ func SetAuthURLParam(key, value string) AuthCodeOption {
|
|||
// that asks for permissions for the required scopes explicitly.
|
||||
//
|
||||
// State is a token to protect the user from CSRF attacks. You must
|
||||
// always provide a non-zero string and validate that it matches the
|
||||
// always provide a non-empty string and validate that it matches the
|
||||
// the state query parameter on your redirect callback.
|
||||
// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
|
||||
//
|
||||
|
@ -129,9 +129,16 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
|||
v := url.Values{
|
||||
"response_type": {"code"},
|
||||
"client_id": {c.ClientID},
|
||||
"redirect_uri": internal.CondVal(c.RedirectURL),
|
||||
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||
"state": internal.CondVal(state),
|
||||
}
|
||||
if c.RedirectURL != "" {
|
||||
v.Set("redirect_uri", c.RedirectURL)
|
||||
}
|
||||
if len(c.Scopes) > 0 {
|
||||
v.Set("scope", strings.Join(c.Scopes, " "))
|
||||
}
|
||||
if state != "" {
|
||||
// TODO(light): Docs say never to omit state; don't allow empty.
|
||||
v.Set("state", state)
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.setValue(v)
|
||||
|
@ -157,12 +164,15 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
|||
// The HTTP client to use is derived from the context.
|
||||
// If nil, http.DefaultClient is used.
|
||||
func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) {
|
||||
return retrieveToken(ctx, c, url.Values{
|
||||
v := url.Values{
|
||||
"grant_type": {"password"},
|
||||
"username": {username},
|
||||
"password": {password},
|
||||
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||
})
|
||||
}
|
||||
if len(c.Scopes) > 0 {
|
||||
v.Set("scope", strings.Join(c.Scopes, " "))
|
||||
}
|
||||
return retrieveToken(ctx, c, v)
|
||||
}
|
||||
|
||||
// Exchange converts an authorization code into a token.
|
||||
|
@ -176,12 +186,14 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor
|
|||
// The code will be in the *http.Request.FormValue("code"). Before
|
||||
// calling Exchange, be sure to validate FormValue("state").
|
||||
func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) {
|
||||
return retrieveToken(ctx, c, url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
"redirect_uri": internal.CondVal(c.RedirectURL),
|
||||
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||
})
|
||||
v := url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
}
|
||||
if c.RedirectURL != "" {
|
||||
v.Set("redirect_uri", c.RedirectURL)
|
||||
}
|
||||
return retrieveToken(ctx, c, v)
|
||||
}
|
||||
|
||||
// Client returns an HTTP client using the provided token.
|
||||
|
@ -292,20 +304,20 @@ var HTTPClient internal.ContextKey
|
|||
// NewClient creates an *http.Client from a Context and TokenSource.
|
||||
// The returned client is not valid beyond the lifetime of the context.
|
||||
//
|
||||
// Note that if a custom *http.Client is provided via the Context it
|
||||
// is used only for token acquisition and is not used to configure the
|
||||
// *http.Client returned from NewClient.
|
||||
//
|
||||
// As a special case, if src is nil, a non-OAuth2 client is returned
|
||||
// using the provided context. This exists to support related OAuth2
|
||||
// packages.
|
||||
func NewClient(ctx context.Context, src TokenSource) *http.Client {
|
||||
if src == nil {
|
||||
c, err := internal.ContextClient(ctx)
|
||||
if err != nil {
|
||||
return &http.Client{Transport: internal.ErrorTransport{Err: err}}
|
||||
}
|
||||
return c
|
||||
return internal.ContextClient(ctx)
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: &Transport{
|
||||
Base: internal.ContextTransport(ctx),
|
||||
Base: internal.ContextClient(ctx).Transport,
|
||||
Source: ReuseTokenSource(nil, src),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -72,6 +72,25 @@ func TestAuthCodeURL_Optional(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestURLUnsafeClientConfig(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("Authorization"), "Basic Q0xJRU5UX0lEJTNGJTNGOkNMSUVOVF9TRUNSRVQlM0YlM0Y="; got != want {
|
||||
t.Errorf("Authorization header = %q; want %q", got, want)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
conf.ClientID = "CLIENT_ID??"
|
||||
conf.ClientSecret = "CLIENT_SECRET??"
|
||||
_, err := conf.Exchange(context.Background(), "exchange-code")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeRequest(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/token" {
|
||||
|
@ -89,7 +108,7 @@ func TestExchangeRequest(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("Failed reading request body: %s.", err)
|
||||
}
|
||||
if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" {
|
||||
if string(body) != "code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL" {
|
||||
t.Errorf("Unexpected exchange payload, %v is found.", string(body))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
@ -133,7 +152,7 @@ func TestExchangeRequest_JSONResponse(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("Failed reading request body: %s.", err)
|
||||
}
|
||||
if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" {
|
||||
if string(body) != "code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL" {
|
||||
t.Errorf("Unexpected exchange payload, %v is found.", string(body))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
@ -259,12 +278,9 @@ func TestExchangeRequest_BadResponse(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tok, err := conf.Exchange(context.Background(), "code")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tok.AccessToken != "" {
|
||||
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||
_, err := conf.Exchange(context.Background(), "code")
|
||||
if err == nil {
|
||||
t.Error("expected error from missing access_token")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,7 +293,7 @@ func TestExchangeRequest_BadResponseType(t *testing.T) {
|
|||
conf := newConf(ts.URL)
|
||||
_, err := conf.Exchange(context.Background(), "exchange-code")
|
||||
if err == nil {
|
||||
t.Error("expected error from invalid access_token type")
|
||||
t.Error("expected error from non-string access_token")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,7 +341,7 @@ func TestPasswordCredentialsTokenRequest(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("Failed reading request body: %s.", err)
|
||||
}
|
||||
expected = "client_id=CLIENT_ID&grant_type=password&password=password1&scope=scope1+scope2&username=user1"
|
||||
expected = "grant_type=password&password=password1&scope=scope1+scope2&username=user1"
|
||||
if string(body) != expected {
|
||||
t.Errorf("res.Body = %q; want %q", string(body), expected)
|
||||
}
|
||||
|
@ -364,7 +380,7 @@ func TestTokenRefreshRequest(t *testing.T) {
|
|||
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||
}
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" {
|
||||
if string(body) != "grant_type=refresh_token&refresh_token=REFRESH_TOKEN" {
|
||||
t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
|
||||
}
|
||||
}))
|
||||
|
@ -400,26 +416,67 @@ func TestFetchWithNoRefreshToken(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTokenRetrieveError(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/token" {
|
||||
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
|
||||
}
|
||||
w.Header().Set("Content-type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(`{"error": "invalid_grant"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
_, err := conf.Exchange(context.Background(), "exchange-code")
|
||||
if err == nil {
|
||||
t.Fatalf("got no error, expected one")
|
||||
}
|
||||
_, ok := err.(*RetrieveError)
|
||||
if !ok {
|
||||
t.Fatalf("got %T error, expected *RetrieveError", err)
|
||||
}
|
||||
// Test error string for backwards compatibility
|
||||
expected := fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", "400 Bad Request", `{"error": "invalid_grant"}`)
|
||||
if errStr := err.Error(); errStr != expected {
|
||||
t.Fatalf("got %#v, expected %#v", errStr, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshToken_RefreshTokenReplacement(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"access_token":"ACCESS TOKEN", "scope": "user", "token_type": "bearer", "refresh_token": "NEW REFRESH TOKEN"}`))
|
||||
w.Write([]byte(`{"access_token":"ACCESS_TOKEN", "scope": "user", "token_type": "bearer", "refresh_token": "NEW_REFRESH_TOKEN"}`))
|
||||
return
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tkr := tokenRefresher{
|
||||
conf: conf,
|
||||
ctx: context.Background(),
|
||||
refreshToken: "OLD REFRESH TOKEN",
|
||||
}
|
||||
tkr := conf.TokenSource(context.Background(), &Token{RefreshToken: "OLD_REFRESH_TOKEN"})
|
||||
tk, err := tkr.Token()
|
||||
if err != nil {
|
||||
t.Errorf("got err = %v; want none", err)
|
||||
return
|
||||
}
|
||||
if tk.RefreshToken != tkr.refreshToken {
|
||||
t.Errorf("tokenRefresher.refresh_token = %q; want %q", tkr.refreshToken, tk.RefreshToken)
|
||||
if want := "NEW_REFRESH_TOKEN"; tk.RefreshToken != want {
|
||||
t.Errorf("RefreshToken = %q; want %q", tk.RefreshToken, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshToken_RefreshTokenPreservation(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"access_token":"ACCESS_TOKEN", "scope": "user", "token_type": "bearer"}`))
|
||||
return
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
const oldRefreshToken = "OLD_REFRESH_TOKEN"
|
||||
tkr := conf.TokenSource(context.Background(), &Token{RefreshToken: oldRefreshToken})
|
||||
tk, err := tkr.Token()
|
||||
if err != nil {
|
||||
t.Fatalf("got err = %v; want none", err)
|
||||
}
|
||||
if tk.RefreshToken != oldRefreshToken {
|
||||
t.Errorf("RefreshToken = %q; want %q", tk.RefreshToken, oldRefreshToken)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package oauth2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
@ -20,7 +21,7 @@ import (
|
|||
// expirations due to client-server time mismatches.
|
||||
const expiryDelta = 10 * time.Second
|
||||
|
||||
// Token represents the crendentials used to authorize
|
||||
// Token represents the credentials used to authorize
|
||||
// the requests to access protected resources on the OAuth 2.0
|
||||
// provider's backend.
|
||||
//
|
||||
|
@ -123,7 +124,7 @@ func (t *Token) expired() bool {
|
|||
if t.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
return t.Expiry.Add(-expiryDelta).Before(time.Now())
|
||||
return t.Expiry.Round(0).Add(-expiryDelta).Before(time.Now())
|
||||
}
|
||||
|
||||
// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
|
||||
|
@ -152,7 +153,23 @@ func tokenFromInternal(t *internal.Token) *Token {
|
|||
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
|
||||
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
|
||||
if err != nil {
|
||||
if rErr, ok := err.(*internal.RetrieveError); ok {
|
||||
return nil, (*RetrieveError)(rErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return tokenFromInternal(tk), nil
|
||||
}
|
||||
|
||||
// RetrieveError is the error returned when the token endpoint returns a
|
||||
// non-2XX HTTP status code.
|
||||
type RetrieveError struct {
|
||||
Response *http.Response
|
||||
// Body is the body that was consumed by reading Response.Body.
|
||||
// It may be truncated.
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (r *RetrieveError) Error() string {
|
||||
return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue