Compare commits
1 Commits
v0.5.6
...
v0.3.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f939a8d67 |
@@ -1,4 +1,2 @@
|
||||
docker-machine*
|
||||
*.log
|
||||
bin
|
||||
cover
|
||||
.git
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,6 +1,2 @@
|
||||
docker-machine*
|
||||
*.log
|
||||
*.iml
|
||||
.idea/
|
||||
./bin
|
||||
cover
|
||||
|
||||
24
.travis.yml
24
.travis.yml
@@ -1,12 +1,14 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: bash
|
||||
services: docker
|
||||
env:
|
||||
matrix:
|
||||
- TARGET_OS=linux TARGET_ARCH=amd64 TARGETS="build validate coverage-send"
|
||||
- TARGET_OS=darwin TARGET_ARCH=amd64 TARGETS="build-x"
|
||||
- TARGET_OS=windows TARGET_ARCH=amd64 TARGETS="build-x"
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.3
|
||||
install:
|
||||
- export GOPATH=${TRAVIS_BUILD_DIR}/Godeps/_workspace:$GOPATH
|
||||
- export PATH=${TRAVIS_BUILD_DIR}/Godeps/_workspace/bin:$PATH
|
||||
- go get -t -v ./...
|
||||
- go get code.google.com/p/go.tools/cmd/cover
|
||||
script:
|
||||
- USE_CONTAINER=true make "$TARGETS"
|
||||
- "[[ \"$(find bin -type f -name docker-machine*)\" != \"\" ]]"
|
||||
- script/validate-dco
|
||||
- script/validate-gofmt
|
||||
- go test -v -short ./...
|
||||
- script/generate-coverage
|
||||
|
||||
427
CHANGELOG.md
427
CHANGELOG.md
@@ -1,427 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
# 0.5.5 (2015-12-28)
|
||||
|
||||
General
|
||||
|
||||
- `env`
|
||||
- Better error message if swarm is down
|
||||
- Add quotes to command if there are spaces in the path
|
||||
- Fix Powershell env hints
|
||||
- Default to cmd shell on windows
|
||||
- Detect fish shell
|
||||
- `scp`
|
||||
- Ignore empty ssh key
|
||||
- `stop`, `start`, `kill`
|
||||
- Add feedback to the user
|
||||
- `rm`
|
||||
- Now works when `config.json` is not found
|
||||
- `ssh`
|
||||
- Disable ControlPath
|
||||
- Log which SSH client is used
|
||||
- `ls`
|
||||
- Listing is now faster by reducing calls to the driver
|
||||
- Shows if the active machine is a swarm cluster
|
||||
|
||||
Build
|
||||
|
||||
- Automate 90% of the release process
|
||||
- Upgrade to Go 1.5.2
|
||||
- Don't build 32bits binaries for Linux and OSX
|
||||
- Prevent makefile from defaulting to using containers
|
||||
|
||||
Misc
|
||||
|
||||
- Update docker-machine version
|
||||
- Updated the bash completion with new options added
|
||||
- Bugsnag: Retrieve windows version on non-english OS
|
||||
|
||||
Drivers
|
||||
|
||||
- Amazon EC2
|
||||
- Convert API calls to official SDK
|
||||
- Make DeviceName configurable
|
||||
- Digital Ocean
|
||||
- Custom SSH port support
|
||||
- Generic
|
||||
- Don't support `kill` since `stop` is not supported
|
||||
- Google
|
||||
- Coreos provisionning
|
||||
- Hyper-V
|
||||
- Lot's of code simplifications
|
||||
- Pre-Check that the user is an Administrator
|
||||
- Pre-Check that the virtual switch exists
|
||||
- Add Environment variables for each flag
|
||||
- Fix how Powershell is detected
|
||||
- VSwitch name should be saved to config.json
|
||||
- Add a flag to set the CPU count
|
||||
- Close handle after copying boot2docker.iso into vm folder - will otherwise keep hyper-v from starting vm
|
||||
- Update Boot2Docker cache in PreCreateCheck phase
|
||||
- OpenStack
|
||||
- Filter floating IPs by tenant ID
|
||||
- Virtualbox
|
||||
- Reject duplicate hostonlyifs Name/IP with clear message
|
||||
- Detect when hostonlyif can't be created. Point to known working version of VirtualBox
|
||||
- Don't create the VM if no hardware virtualization is available and add a flag to force create
|
||||
- Add `VBox.log` to bugsnag crashreport
|
||||
- Update Boot2Docker cache in PreCreateCheck phase
|
||||
- Detect Incompatibility with Hyper-v
|
||||
- VSphere
|
||||
- Rewrite driver to work with govmomi instead of wrapping govc
|
||||
- All
|
||||
- Change host restart to use the driver implementation
|
||||
- Fix truncated logs
|
||||
- Increase heartbeat interval and timeout
|
||||
|
||||
Provisioners
|
||||
|
||||
- Download latest Boot2Docker if it is out-of-date
|
||||
- Add swarm config to coreos
|
||||
- All provisioners now honor `engine-install-url`
|
||||
|
||||
# 0.5.4 (2015-12-28)
|
||||
|
||||
This is a patch release to fix a regression with STDOUT/STDERR behavior (#2587).
|
||||
|
||||
# 0.5.3 (2015-12-14)
|
||||
|
||||
**Please note**: With this release Machine will be reverting back to distribution in a single binary, which is more efficient on bandwidth and hard disk space. All the core driver plugins are now included in the main binary. You will want to delete the old driver binaries that you might have in your path.
|
||||
|
||||
e.g.:
|
||||
|
||||
```console
|
||||
$ rm /usr/local/bin/docker-machine-driver-{amazonec2,azure,digitalocean,exoscale,generic,google,hyperv,none,openstack,rackspace,softlayer,virtualbox,vmwarefusion,vmwarevcloudair,vmwarevsphere}
|
||||
```
|
||||
|
||||
Non-core driver plugins should still work as intended (in externally distributed binaries of the form `docker-machine-driver-name`. Please report any issues you encounter them with externally loaded plugins.
|
||||
|
||||
General
|
||||
|
||||
- Optionally report crashes to Bugsnag to help us improve docker-machine
|
||||
- Fix multiple nil dereferences in `docker-machine ls` command
|
||||
- Improve the build and CI
|
||||
- `docker-machine env` now supports emacs
|
||||
- Run Swarm containers in provisioning step using Docker API instead of SSH/shell
|
||||
- Show docker daemon version in `docker-machine ls`
|
||||
- `docker-machine ls` can filter by engine label
|
||||
- `docker-machine ls` filters are case insensitive
|
||||
- `--timeout` flag for `docker-machine ls`
|
||||
- Logs use `logrus` library
|
||||
- Swarm container network is now `host`
|
||||
- Added advertise flag to Swarm manager template
|
||||
- Fix `help` flag for `docker-machine ssh`
|
||||
- Add confirmation `-y` flag to `docker-machine rm`
|
||||
- Fix `docker-machine config` for fish
|
||||
- Embed all core drivers in `docker-machine` binary to reduce the bundle from 120M to 15M
|
||||
|
||||
Drivers
|
||||
|
||||
- Generic
|
||||
- Support password protected ssh keys though ssh-agent
|
||||
- Support DNS names
|
||||
- Virtualbox
|
||||
- Show a warning if virtualbox is too old
|
||||
- Recognize yet another Hardware Virtualization issue pattern
|
||||
- Fix Hardware Virtualization on Linux/AMD
|
||||
- Add the `--virtualbox-host-dns-resolver` flag
|
||||
- Allow virtualbox DNSProxy override
|
||||
- Google
|
||||
- Open firewall port for Swarm when needed
|
||||
- VMware Fusion
|
||||
- Explicitly set umask before invoking vmrun in vmwarefusion
|
||||
- Activate the plugin only on OSX
|
||||
- Add id/gid option to mount when using vmhgfs
|
||||
- Fix for vSphere driver boot2docker ISO issues
|
||||
- Digital Ocean
|
||||
- Support for creating Droplets with Cloud-init User Data
|
||||
- Openstack
|
||||
- Sanitize keynames by replacing dots with underscores
|
||||
- All
|
||||
- Most base images are now set to `Ubuntu 15.10`
|
||||
- Fix compatibility with drivers developed with docker-machine 0.5.0
|
||||
- Better error report for broken/incompatible drivers
|
||||
- Don't break `config.json` configuration when the disk is full
|
||||
|
||||
Provisioners
|
||||
|
||||
- Increase timeout for installing boot2docker
|
||||
- Support `Ubuntu 15.10`
|
||||
|
||||
Misc
|
||||
|
||||
- Improve the documentation
|
||||
- Update known drivers list
|
||||
|
||||
# 0.5.2 (2015-11-30)
|
||||
|
||||
General
|
||||
|
||||
- Bash autocompletion and helpers fixed
|
||||
- Remove `RawDriver` from `config.json` - Driver parameters can now be edited
|
||||
directly again in this file.
|
||||
- Change fish `env` variable setting to be global
|
||||
- Add `docker-machine version` command
|
||||
- Move back to normal `codegangsta/cli` upstream
|
||||
- `--tls-san` flag for extra SANs
|
||||
|
||||
Drivers
|
||||
|
||||
- Fix `GetURL` IPv6 compatibility
|
||||
- Add documentation page for available 3rd party drivers
|
||||
- VirtualBox
|
||||
- Support for shared folders and virtualization detection on Linux hosts
|
||||
- Improved detection of invalid host-only interface settings
|
||||
- Google
|
||||
- Update default images
|
||||
- VMware Fusion
|
||||
- Add option to disable shared folder
|
||||
- Generic
|
||||
- New environment variables for flags
|
||||
|
||||
Provisioners
|
||||
|
||||
- Support for Ubuntu >=15.04. This means Ubuntu machines can be created which
|
||||
work with `overlay` driver of lib network.
|
||||
- Fix issue with current netstat / daemon availability checking
|
||||
|
||||
# 0.5.1 (2015-11-16)
|
||||
|
||||
- Fixed boot2docker VM import regression
|
||||
- Fix regression breaking `docker-machine env -u` to unset environment variables
|
||||
- Enhanced virtualization capability detection and `VBoxManage` path detection
|
||||
- Properly lock VirtualBox access when running several commands concurrently
|
||||
- Allow plugins to write to STDOUT without `--debug` enabled
|
||||
- Fix Rackspace driver regression
|
||||
- Support colons in `docker-machine scp` filepaths
|
||||
- Pass environment variables for provisioned Engines to Swarm as well
|
||||
- Various enhancements around boot2docker ISO upgrade (progress bar, increased timeout)
|
||||
|
||||
# 0.5.0 (2015-11-1)
|
||||
|
||||
- General
|
||||
- Add pluggable driver model
|
||||
- Clean up code to be more modular and reusable in `libmachine`
|
||||
- Add `--github-api-token` for situations where users are getting rate limited
|
||||
by GitHub attempting to get the current `boot2docker.iso` version
|
||||
- Various enhancements around the Makefile and build toolchain (still an active WIP)
|
||||
- Disable SSH multiplex explicitly in commands run with the "External" client
|
||||
- Show "-" for "inactive" machines instead of nothing
|
||||
- Make daemon status detection more robust
|
||||
- Provisioners
|
||||
- New CoreOS, SUSE, and Arch Linux provisioners
|
||||
- Fixes around package installation / upgrade code on Debian and Ubuntu
|
||||
- CLI
|
||||
- Support for regular expression pattern matching and matching by names in `ls --filter`
|
||||
- `--no-proxy` flag for `env` (sets `NO_PROXY` in addition to other environment variables)
|
||||
- Drivers
|
||||
- `openstack`
|
||||
- `--openstack-ip-version` parameter
|
||||
- `--openstack-active-timeout` parameter
|
||||
- `google`
|
||||
- fix destructive behavior of `start` / `stop`
|
||||
- `hyperv`
|
||||
- fix issues with PowerShell
|
||||
- `vmwarefusion`
|
||||
- some issues with shared folders fixed
|
||||
- `--vmwarefusion-configdrive-url` option for configuration via `cloud-init`
|
||||
- `amazonec2`
|
||||
- `--amazonec2-use-private-address` option to use private networking
|
||||
- `virtualbox`
|
||||
- Enhancements around robustness of the created host-only network
|
||||
- Fix IPv6 network mask prefix parsing
|
||||
- `--virtualbox-no-share` option to disable the automatic home directory mount
|
||||
- `--virtualbox-hostonly-nictype` and `--virtualbox-hostonly-nicpromisc` for controlling settings around the created hostonly NIC
|
||||
|
||||
# 0.4.1 (2015-08)
|
||||
|
||||
- Fixes `upgrade` functionality on Debian based systems
|
||||
- Fixes `upgrade` functionality on Ubuntu based systems
|
||||
|
||||
# 0.4.0 (2015-08-11)
|
||||
|
||||
## Updates
|
||||
|
||||
- HTTP Proxy support for Docker Engine
|
||||
- RedHat distros now use Docker Yum repositories
|
||||
- Ability to set environment variables in the Docker Engine
|
||||
- Internal libmachine updates for stability
|
||||
|
||||
## Drivers
|
||||
|
||||
- Google:
|
||||
- Preemptible instances
|
||||
- Static IP support
|
||||
|
||||
## Fixes
|
||||
|
||||
- Swarm Discovery Flag is verified
|
||||
- Timeout added to `ls` command to prevent hangups
|
||||
- SSH command failure now reports information about error
|
||||
- Configuration migration updates
|
||||
|
||||
# 0.3.0 (2015-06-18)
|
||||
|
||||
## Features
|
||||
|
||||
- Engine option configuration (ability to configure all engine options)
|
||||
- Swarm option configuration (ability to configure all swarm options)
|
||||
- New Provisioning system to allow for greater flexibility and stability for installing and configuring Docker
|
||||
- New Provisioners
|
||||
- Rancher OS
|
||||
- RedHat Enterprise Linux 7.0+ (experimental)
|
||||
- Fedora 21+ (experimental)
|
||||
- Debian 8+ (experimental)
|
||||
- PowerShell support (configure Windows Docker CLI)
|
||||
- Command Prompt (cmd.exe) support (configure Windows Docker CLI)
|
||||
- Filter command help by driver
|
||||
- Ability to import Boot2Docker instances
|
||||
- Boot2Docker CLI migration guide (experimental)
|
||||
- Format option for `inspect` command
|
||||
- New logging output format to improve readability and display across platforms
|
||||
- Updated "active" machine concept - now is implicit according to `DOCKER_HOST` environment variable. Note: this removes the implicit "active" machine and can no longer be specified with the `active` command. You change the "active" host by using the `env` command instead.
|
||||
- Specify Swarm version (`--swarm-image` flag)
|
||||
|
||||
## Drivers
|
||||
|
||||
- New: Exoscale Driver
|
||||
- New: Generic Driver (provision any host with supported base OS and SSH)
|
||||
- Amazon EC2
|
||||
- SSH user is configurable
|
||||
- Support for Spot instances
|
||||
- Add option to use private address only
|
||||
- Base AMI updated to 20150417
|
||||
- Google
|
||||
- Support custom disk types
|
||||
- Updated base image to v20150316
|
||||
- Openstack
|
||||
- Support for Keystone v3 domains
|
||||
- Rackspace
|
||||
- Misc fixes including environment variable for Flavor Id and stability
|
||||
- Softlayer
|
||||
- Enable local disk as provisioning option
|
||||
- Fixes for SSH access errors
|
||||
- Fixed bug where public IP would always be returned when requesting private
|
||||
- Add support for specifying public and private VLAN IDs
|
||||
- VirtualBox
|
||||
- Use Intel network interface driver (adds great stability)
|
||||
- Stability fixes for NAT access
|
||||
- Use DNS pass through
|
||||
- Default CPU to single core for improved performance
|
||||
- Enable shared folder support for Windows hosts
|
||||
- VMware Fusion
|
||||
- Boot2Docker ISO updated
|
||||
- Shared folder support
|
||||
|
||||
## Fixes
|
||||
|
||||
- Provisioning improvements to ensure Docker is available
|
||||
- SSH improvements for provisioning stability
|
||||
- Fixed SSH key generation bug on Windows
|
||||
- Help formatting for improved readability
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- "Short-Form" name reference no longer supported Instead of "docker-machine " implying the active host you must now use docker-machine
|
||||
- VMware shared folders require Boot2Docker 1.7
|
||||
|
||||
## Special Thanks
|
||||
|
||||
We would like to thank all contributors. Machine would not be where it is
|
||||
without you. We would also like to give special thanks to the following
|
||||
contributors for outstanding contributions to the project:
|
||||
|
||||
- @frapposelli for VMware updates and fixes
|
||||
- @hairyhenderson for several improvements to Softlayer driver, inspect formatting and lots of fixes
|
||||
- @ibuildthecloud for rancher os provisioning
|
||||
- @sthulb for portable SSH library
|
||||
- @vincentbernat for exoscale
|
||||
- @zchee for Amazon updates and great doc updates
|
||||
|
||||
# 0.2.0 (2015-04-16)
|
||||
|
||||
Core Stability and Driver Updates
|
||||
|
||||
## Core
|
||||
|
||||
- Support for system proxy environment
|
||||
- New command to regenerate TLS certificates
|
||||
- Note: this will restart the Docker engine to apply
|
||||
- Updates to driver operations (create, start, stop, etc) for better reliability
|
||||
- New internal `libmachine` package for internal api (not ready for public usage)
|
||||
- Updated Driver Interface
|
||||
- [Driver Spec](https://github.com/docker/machine/blob/master/docs/DRIVER_SPEC.md)
|
||||
- Removed host provisioning from Drivers to enable a more consistent install
|
||||
- Removed SSH commands from each Driver for more consistent operations
|
||||
- Swarm: machine now uses Swarm default binpacking strategy
|
||||
|
||||
## Driver Updates
|
||||
|
||||
- All drivers updated to new Driver interface
|
||||
- Amazon EC2
|
||||
- Better checking for subnets on creation
|
||||
- Support for using Private IPs in VPC
|
||||
- Fixed bug with duplicate security group authorization with Swarm
|
||||
- Support for IAM instance profile
|
||||
- Fixed bug where IP was not properly detected upon stop
|
||||
- DigitalOcean
|
||||
- IPv6 support
|
||||
- Backup option
|
||||
- Private Networking
|
||||
- Openstack / Rackspace
|
||||
- Gophercloud updated to latest version
|
||||
- New insecure flag to disable TLS (use with caution)
|
||||
- Google
|
||||
- Google source image updated
|
||||
- Ability to specify auth token via file
|
||||
- VMware Fusion
|
||||
- Paravirtualized driver for disk (pvscsi)
|
||||
- Enhanced paravirtualized NIC (vmxnet3)
|
||||
- Power option updates
|
||||
- SSH keys persistent across reboots
|
||||
- Stop now gracefully stops VM
|
||||
- vCPUs now match host CPUs
|
||||
- SoftLayer
|
||||
- Fixed provision bug where `curl` was not present
|
||||
- VirtualBox
|
||||
- Correct power operations with Saved VM state
|
||||
- Fixed bug where image option was ignored
|
||||
|
||||
## CLI
|
||||
|
||||
- Auto-regeneration of TLS certificates when TLS error is detected
|
||||
- Note: this will restart the Docker engine to apply
|
||||
- Minor UI updates including improved sorting and updated command docs
|
||||
- Bug with `config` and `env` with spaces fixed
|
||||
- Note: you now must use `eval $(docker-machine env machine)` to load environment settings
|
||||
- Updates to better support `fish` shell
|
||||
- Use `--tlsverify` for both `config` and `env` commands
|
||||
- Commands now use eval for better interoperability with shell
|
||||
|
||||
## Testing
|
||||
|
||||
- New integration test framework (bats)
|
||||
|
||||
# 0.1.0 (2015-02-26)
|
||||
|
||||
Initial beta release.
|
||||
|
||||
- Provision Docker Engines using multiple drivers
|
||||
- Provide light management for the machines
|
||||
- Create, Start, Stop, Restart, Kill, Remove, SSH
|
||||
- Configure the Docker Engine for secure communication (TLS)
|
||||
- Easily switch target machine for fast configuration of Docker Engine client
|
||||
- Provision Swarm clusters (experimental)
|
||||
|
||||
## Included drivers
|
||||
|
||||
- Amazon EC2
|
||||
- Digital Ocean
|
||||
- Google
|
||||
- Microsoft Azure
|
||||
- Microsoft Hyper-V
|
||||
- Openstack
|
||||
- Rackspace
|
||||
- VirtualBox
|
||||
- VMware Fusion
|
||||
- VMware vCloud Air
|
||||
- VMware vSphere
|
||||
91
CHANGES.md
Normal file
91
CHANGES.md
Normal file
@@ -0,0 +1,91 @@
|
||||
Changelog
|
||||
==========
|
||||
|
||||
# 0.2.0 (2015-04-16)
|
||||
|
||||
Core Stability and Driver Updates
|
||||
|
||||
## Core
|
||||
|
||||
- Support for system proxy environment
|
||||
- New command to regenerate TLS certificates
|
||||
- Note: this will restart the Docker engine to apply
|
||||
- Updates to driver operations (create, start, stop, etc) for better reliability
|
||||
- New internal `libmachine` package for internal api (not ready for public usage)
|
||||
- Updated Driver Interface
|
||||
- [Driver Spec](https://github.com/docker/machine/blob/master/docs/DRIVER_SPEC.md)
|
||||
- Removed host provisioning from Drivers to enable a more consistent install
|
||||
- Removed SSH commands from each Driver for more consistent operations
|
||||
- Swarm: machine now uses Swarm default binpacking strategy
|
||||
|
||||
## Driver Updates
|
||||
|
||||
- All drivers updated to new Driver interface
|
||||
- Amazon EC2
|
||||
- Better checking for subnets on creation
|
||||
- Support for using Private IPs in VPC
|
||||
- Fixed bug with duplicate security group authorization with Swarm
|
||||
- Support for IAM instance profile
|
||||
- Fixed bug where IP was not properly detected upon stop
|
||||
- DigitalOcean
|
||||
- IPv6 support
|
||||
- Backup option
|
||||
- Private Networking
|
||||
- Openstack / Rackspace
|
||||
- Gophercloud updated to latest version
|
||||
- New insecure flag to disable TLS (use with caution)
|
||||
- Google
|
||||
- Google source image updated
|
||||
- Ability to specify auth token via file
|
||||
- VMware Fusion
|
||||
- Paravirtualized driver for disk (pvscsi)
|
||||
- Enhanced paravirtualized NIC (vmxnet3)
|
||||
- Power option updates
|
||||
- SSH keys persistent across reboots
|
||||
- Stop now gracefully stops VM
|
||||
- vCPUs now match host CPUs
|
||||
- SoftLayer
|
||||
- Fixed provision bug where `curl` was not present
|
||||
- VirtualBox
|
||||
- Correct power operations with Saved VM state
|
||||
- Fixed bug where image option was ignored
|
||||
|
||||
## CLI
|
||||
|
||||
- Auto-regeneration of TLS certificates when TLS error is detected
|
||||
- Note: this will restart the Docker engine to apply
|
||||
- Minor UI updates including improved sorting and updated command docs
|
||||
- Bug with `config` and `env` with spaces fixed
|
||||
- Note: you now must use `eval $(docker-machine env machine)` to load environment settings
|
||||
- Updates to better support `fish` shell
|
||||
- Use `--tlsverify` for both `config` and `env` commands
|
||||
- Commands now use eval for better interoperability with shell
|
||||
|
||||
## Testing
|
||||
- New integration test framework (bats)
|
||||
|
||||
|
||||
# 0.1.0 (2015-02-26)
|
||||
|
||||
Initial beta release.
|
||||
|
||||
- Provision Docker Engines using multiple drivers
|
||||
- Provide light management for the machines
|
||||
- Create, Start, Stop, Restart, Kill, Remove, SSH
|
||||
- Configure the Docker Engine for secure communication (TLS)
|
||||
- Easily switch target machine for fast configuration of Docker Engine client
|
||||
- Provision Swarm clusters (experimental)
|
||||
|
||||
## Included drivers
|
||||
|
||||
- Amazon EC2
|
||||
- Digital Ocean
|
||||
- Google
|
||||
- Microsoft Azure
|
||||
- Microsoft Hyper-V
|
||||
- Openstack
|
||||
- Rackspace
|
||||
- VirtualBox
|
||||
- VMware Fusion
|
||||
- VMware vCloud Air
|
||||
- VMware vSphere
|
||||
231
CONTRIBUTING.md
231
CONTRIBUTING.md
@@ -2,7 +2,6 @@
|
||||
|
||||
[](https://godoc.org/github.com/docker/machine)
|
||||
[](https://travis-ci.org/docker/machine)
|
||||
[](https://coveralls.io/github/docker/machine?branch=upstream-master)
|
||||
|
||||
Want to hack on Machine? Awesome! Here are instructions to get you
|
||||
started.
|
||||
@@ -11,157 +10,106 @@ Machine is a part of the [Docker](https://www.docker.com) project, and follows
|
||||
the same rules and principles. If you're already familiar with the way
|
||||
Docker does things, you'll feel right at home.
|
||||
|
||||
Otherwise, please read [Docker's contributions
|
||||
guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md).
|
||||
|
||||
# Building
|
||||
Otherwise, go read
|
||||
[Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md).
|
||||
|
||||
The requirements to build Machine are:
|
||||
|
||||
1. A running instance of Docker or a Golang 1.5.2 development environment
|
||||
2. The `bash` shell
|
||||
3. [Make](https://www.gnu.org/software/make/)
|
||||
1. A running instance of Docker
|
||||
2. The `bash` shell
|
||||
|
||||
## Build using Docker containers
|
||||
To build, run:
|
||||
|
||||
To build the `docker-machine` binary using containers, simply run:
|
||||
$ script/build
|
||||
|
||||
$ export USE_CONTAINER=true
|
||||
$ make build
|
||||
From the Machine repository's root. Machine will run the build inside of a
|
||||
Docker container and the compiled binaries will appear in the project directory
|
||||
on the host.
|
||||
|
||||
## Local Go development environment
|
||||
By default, Machine will run a build which cross-compiles binaries for a variety
|
||||
of architectures and operating systems. If you know that you are only compiling
|
||||
for a particular architecture and/or operating system, you can speed up
|
||||
compilation by overriding the default argument that the build script passes
|
||||
to [gox](https://github.com/mitchellh/gox). This is very useful if you want
|
||||
to iterate quickly on a new feature, bug fix, etc.
|
||||
|
||||
Make sure the source code directory is under a correct directory structure to use Go 1.5 vendoring;
|
||||
example of cloning and preparing the correct environment `GOPATH`:
|
||||
For instance, if you only want to compile for use on OS X with the x86_64 arch,
|
||||
run:
|
||||
|
||||
mkdir docker-machine
|
||||
cd docker-machine
|
||||
export GOPATH="$PWD"
|
||||
go get github.com/docker/machine
|
||||
cd src/github.com/docker/machine
|
||||
$ script/build -osarch="darwin/amd64"
|
||||
|
||||
At this point, simply run:
|
||||
If you don't need to run the `docker build` to generate the image on each
|
||||
compile, i.e. if you have built the image already, you can skip the image build
|
||||
using the `SKIP_BUILD` environment variable, for instance:
|
||||
|
||||
$ make build
|
||||
$ SKIP_BUILD=1 script/build -osarch="darwin/amd64"
|
||||
|
||||
## Built binary
|
||||
If you have any questions we're in #docker-machine on Freenode.
|
||||
|
||||
After the build is complete a `bin/docker-machine` binary will be created.
|
||||
## Unit Tests
|
||||
|
||||
You may call:
|
||||
To run the unit tests for the whole project, using the following script:
|
||||
|
||||
$ make clean
|
||||
$ script/test
|
||||
|
||||
to clean-up build results.
|
||||
This will run the unit tests inside of a container, so you don't have to worry
|
||||
about configuring your environment properly before doing so.
|
||||
|
||||
## Tests and validation
|
||||
To run the unit tests for only a specific subdirectory of the project, you can
|
||||
pass an argument to that script to specify which directory, e.g.:
|
||||
|
||||
We use the usual `go` tools for this, to run those commands you need at least the linter which you can
|
||||
install with `go get -u github.com/golang/lint/golint`
|
||||
|
||||
To run basic validation (dco, fmt), and the project unit tests, call:
|
||||
|
||||
$ make test
|
||||
|
||||
If you want more indepth validation (vet, lint), and all tests with race detection, call:
|
||||
|
||||
$ make validate
|
||||
$ script/test ./drivers/amazonec2
|
||||
|
||||
If you make a pull request, it is highly encouraged that you submit tests for
|
||||
the code that you have added or modified in the same pull request.
|
||||
|
||||
## Code Coverage
|
||||
|
||||
To generate an html code coverage report of the Machine codebase, run:
|
||||
Machine includes a script to check for missing `*_test.go` files and to generate
|
||||
an [HTML-based representation of which code is covered by tests](http://blog.golang.org/cover#TOC_5.).
|
||||
|
||||
make coverage-serve
|
||||
To run the code coverage script, execute:
|
||||
|
||||
And navigate to <http://localhost:8000> (hit `CTRL+C` to stop the server).
|
||||
```console
|
||||
$ ./script/coverage serve
|
||||
```
|
||||
|
||||
### Native build
|
||||
You will see the results of the code coverage check as they come in.
|
||||
|
||||
Alternatively, if you are building natively, you can simply run:
|
||||
|
||||
make coverage-html
|
||||
|
||||
This will generate and open the report file:
|
||||
This will also generate the code coverage website and serve it from a container
|
||||
on port 8000. By default, `/` will show you the source files from the base
|
||||
directory, and you can navigate to the coverage for any particular subdirectory
|
||||
of the Docker Machine repo's root by going to that path. For instance, to see
|
||||
the coverage for the VirtualBox driver's package, browse to `/drivers/virtualbox`.
|
||||
|
||||

|
||||
|
||||
## List of all targets
|
||||
|
||||
### High-level targets
|
||||
|
||||
make clean
|
||||
make build
|
||||
make test
|
||||
make validate
|
||||
|
||||
### Advanced build targets
|
||||
|
||||
Build for all supported OSes and architectures (binaries will be in the `bin` project subfolder):
|
||||
|
||||
make build-x
|
||||
|
||||
Build for a specific list of OSes and architectures:
|
||||
|
||||
TARGET_OS=linux TARGET_ARCH="amd64 arm" make build-x
|
||||
|
||||
You can further control build options through the following environment variables:
|
||||
|
||||
DEBUG=true # enable debug build
|
||||
STATIC=true # build static (note: when cross-compiling, the build is always static)
|
||||
VERBOSE=true # verbose output
|
||||
PREFIX=folder # put binaries in another folder (not the default `./bin`)
|
||||
|
||||
Scrub build results:
|
||||
|
||||
make build-clean
|
||||
|
||||
### Coverage targets
|
||||
|
||||
make coverage-html
|
||||
make coverage-serve
|
||||
make coverage-send
|
||||
make coverage-generate
|
||||
make coverage-clean
|
||||
|
||||
### Tests targets
|
||||
|
||||
make test-short
|
||||
make test-long
|
||||
make test-integration
|
||||
|
||||
### Validation targets
|
||||
|
||||
make fmt
|
||||
make vet
|
||||
make lint
|
||||
make dco
|
||||
|
||||
### Save and restore dependencies
|
||||
|
||||
make dep-save
|
||||
make dep-restore
|
||||
You can hit `CTRL+C` to stop the server.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Setup
|
||||
|
||||
We use [BATS](https://github.com/sstephenson/bats) for integration testing, so,
|
||||
first make sure to [install it](https://github.com/sstephenson/bats#installing-bats-from-source).
|
||||
We utilize [BATS](https://github.com/sstephenson/bats) for integration testing.
|
||||
This runs tests against the generated binary. To use, first make sure to
|
||||
[install BATS](https://github.com/sstephenson/bats). Then run `./script/build`
|
||||
to generate the binary for your system.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
You first need to build, calling `make build`.
|
||||
Once you have the binary, the integration tests can be invoked using the
|
||||
`test/integration/run-bats.sh` wrapper script.
|
||||
|
||||
You can then invoke integration tests calling `DRIVER=foo make test-integration TESTSUITE`, where `TESTSUITE` is
|
||||
one of the `test/integration` subfolder, and `foo` is the specific driver you want to test.
|
||||
Using this wrapper script, you can invoke a test or subset of tests for a
|
||||
particular driver. To set the driver, use the `DRIVER` environment variable.
|
||||
|
||||
Examples:
|
||||
The following examples are all shown relative to the project's root directory,
|
||||
but you should be able to invoke them from any directory without issue.
|
||||
|
||||
To invoke just one test:
|
||||
|
||||
```console
|
||||
$ DRIVER=virtualbox make test-integration test/integration/core/core-commands.bats
|
||||
$ DRIVER=virtualbox ./test/integration/run-bats.sh test/integration/core/core-commands.bats
|
||||
✓ virtualbox: machine should not exist
|
||||
✓ virtualbox: create
|
||||
✓ virtualbox: ls
|
||||
@@ -185,13 +133,26 @@ Cleaning up machines...
|
||||
Successfully removed bats-virtualbox-test
|
||||
```
|
||||
|
||||
To invoke a shared test with a different driver:
|
||||
|
||||
```console
|
||||
$ DRIVER=digitalocean ./test/integration/run-bats.sh test/integration/core/core-commands.bats
|
||||
...
|
||||
```
|
||||
|
||||
To invoke a directory of tests recursively:
|
||||
|
||||
```console
|
||||
$ DRIVER=virtualbox make test-integration test/integration/core/
|
||||
$ DRIVER=virtualbox ./test/integration/run-bats.sh test/integration/core/
|
||||
...
|
||||
```
|
||||
|
||||
If you want to invoke a group of tests across two or more different drivers at
|
||||
once (e.g. every test in the `drivers` directory), at the time of writing there
|
||||
is no first-class support to do so - you will have to write your own wrapper
|
||||
scripts, bash loops, etc. However, in the future, this may gain first-class
|
||||
support as usage patterns become more clear.
|
||||
|
||||
### Extra Create Arguments
|
||||
|
||||
In some cases, for instance to test the creation of a specific base OS (e.g.
|
||||
@@ -202,13 +163,15 @@ Keep in mind that Machine supports environment variables for many of these
|
||||
flags. So, for instance, you could run the command (substituting, of course,
|
||||
the proper secrets):
|
||||
|
||||
$ DRIVER=amazonec2 \
|
||||
AWS_VPC_ID=vpc-xxxxxxx \
|
||||
AWS_SECRET_ACCESS_KEY=yyyyyyyyyyyyy \
|
||||
AWS_ACCESS_KEY_ID=zzzzzzzzzzzzzzzz \
|
||||
AWS_AMI=ami-12663b7a \
|
||||
AWS_SSH_USER=ec2-user \
|
||||
make test-integration test/integration/core
|
||||
```
|
||||
$ DRIVER=amazonec2 \
|
||||
AWS_VPC_ID=vpc-xxxxxxx \
|
||||
AWS_SECRET_ACCESS_KEY=yyyyyyyyyyyyy \
|
||||
AWS_ACCESS_KEY_ID=zzzzzzzzzzzzzzzz \
|
||||
AWS_AMI=ami-12663b7a \
|
||||
AWS_SSH_USER=ec2-user \
|
||||
./test/integration/run-bats.sh test/integration/core
|
||||
```
|
||||
|
||||
in order to run the core tests on Red Hat Enterprise Linux on Amazon.
|
||||
|
||||
@@ -220,11 +183,11 @@ guide you.
|
||||
|
||||
At the time of writing, there is:
|
||||
|
||||
1. A `core` directory which contains tests that are applicable to all drivers.
|
||||
2. A `drivers` directory which contains tests that are applicable only to
|
||||
specific drivers with sub-directories for each provider.
|
||||
3. A `cli` directory which is meant for testing functionality of the command
|
||||
line interface, without much regard for driver-specific details.
|
||||
1. A `core` directory which contains tests that are applicable to all drivers.
|
||||
2. A `drivers` directory which contains tests that are applicable only to
|
||||
specific drivers with sub-directories for each provider.
|
||||
3. A `cli` directory which is meant for testing functionality of the command
|
||||
line interface, without much regard for driver-specific details.
|
||||
|
||||
### Guidelines
|
||||
|
||||
@@ -233,15 +196,15 @@ work in progress, but here are some general guidelines from the maintainers:
|
||||
|
||||
1. Ideally, each test file should have only one concern.
|
||||
2. Tests generally should not spin up more than one machine unless the test is
|
||||
deliberately testing something which involves multiple machines, such as an `ls`
|
||||
test which involves several machines, or a test intended to create and check
|
||||
some property of a Swarm cluster.
|
||||
deliberately testing something which involves multiple machines, such as an `ls`
|
||||
test which involves several machines, or a test intended to create and check
|
||||
some property of a Swarm cluster.
|
||||
3. BATS will print the output of commands executed during a test if the test
|
||||
fails. This can be useful, for instance to dump the magic `$output` variable
|
||||
that BATS provides and/or to get debugging information.
|
||||
fails. This can be useful, for instance to dump the magic `$output` variable
|
||||
that BATS provides and/or to get debugging information.
|
||||
4. It is not strictly needed to clean up the machines as part of the test. The
|
||||
BATS wrapper script has a hook to take care of cleaning up all created machines
|
||||
after each test.
|
||||
BATS wrapper script has a hook to take care of cleaning up all created machines
|
||||
after each test.
|
||||
|
||||
# Drivers
|
||||
|
||||
@@ -249,11 +212,11 @@ Docker Machine has several included drivers that supports provisioning hosts
|
||||
in various providers. If you wish to contribute a driver, we ask the following
|
||||
to ensure we keep the driver in a consistent and stable state:
|
||||
|
||||
- Address issues filed against this driver in a timely manner
|
||||
- Review PRs for the driver
|
||||
- Be responsible for maintaining the infrastructure to run unit tests
|
||||
and integration tests on the new supported environment
|
||||
- Participate in a weekly driver maintainer meeting
|
||||
- Address issues filed against this driver in a timely manner
|
||||
- Review PRs for the driver
|
||||
- Be responsible for maintaining the infrastructure to run unit tests
|
||||
and integration tests on the new supported environment
|
||||
- Participate in a weekly driver maintainer meeting
|
||||
|
||||
If you can commit to those, the next step is to make sure the driver adheres
|
||||
to the [spec](https://github.com/docker/machine/blob/master/docs/DRIVER_SPEC.md).
|
||||
|
||||
17
Dockerfile
17
Dockerfile
@@ -1,13 +1,16 @@
|
||||
FROM golang:1.5.2
|
||||
FROM golang:1.3-cross
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends openssh-client
|
||||
|
||||
RUN go get github.com/golang/lint/golint \
|
||||
github.com/mattn/goveralls \
|
||||
golang.org/x/tools/cover \
|
||||
github.com/tools/godep \
|
||||
github.com/aktau/github-release
|
||||
# TODO: Vendor these `go get` commands using Godep.
|
||||
RUN go get github.com/mitchellh/gox
|
||||
RUN go get github.com/aktau/github-release
|
||||
RUN go get github.com/tools/godep
|
||||
RUN go get code.google.com/p/go.tools/cmd/cover
|
||||
|
||||
ENV GOPATH /go/src/github.com/docker/machine/Godeps/_workspace:/go
|
||||
ENV MACHINE_BINARY /go/src/github.com/docker/machine/docker-machine
|
||||
ENV USER root
|
||||
|
||||
WORKDIR /go/src/github.com/docker/machine
|
||||
|
||||
ADD . /go/src/github.com/docker/machine
|
||||
RUN mkdir bin
|
||||
|
||||
261
Godeps/Godeps.json
generated
261
Godeps/Godeps.json
generated
@@ -1,188 +1,139 @@
|
||||
{
|
||||
"ImportPath": "github.com/docker/machine",
|
||||
"GoVersion": "go1.5.2",
|
||||
"Packages": [
|
||||
"github.com/docker/machine",
|
||||
"github.com/docker/machine/cmd",
|
||||
"github.com/docker/machine/commands",
|
||||
"github.com/docker/machine/commands/commandstest",
|
||||
"github.com/docker/machine/commands/mcndirs",
|
||||
"github.com/docker/machine/drivers/amazonec2",
|
||||
"github.com/docker/machine/drivers/azure",
|
||||
"github.com/docker/machine/drivers/digitalocean",
|
||||
"github.com/docker/machine/drivers/errdriver",
|
||||
"github.com/docker/machine/drivers/exoscale",
|
||||
"github.com/docker/machine/drivers/fakedriver",
|
||||
"github.com/docker/machine/drivers/generic",
|
||||
"github.com/docker/machine/drivers/google",
|
||||
"github.com/docker/machine/drivers/hyperv",
|
||||
"github.com/docker/machine/drivers/none",
|
||||
"github.com/docker/machine/drivers/openstack",
|
||||
"github.com/docker/machine/drivers/rackspace",
|
||||
"github.com/docker/machine/drivers/softlayer",
|
||||
"github.com/docker/machine/drivers/virtualbox",
|
||||
"github.com/docker/machine/drivers/vmwarefusion",
|
||||
"github.com/docker/machine/drivers/vmwarevcloudair",
|
||||
"github.com/docker/machine/drivers/vmwarevsphere",
|
||||
"github.com/docker/machine/drivers/vmwarevsphere/errors",
|
||||
"github.com/docker/machine/libmachine",
|
||||
"github.com/docker/machine/libmachine/auth",
|
||||
"github.com/docker/machine/libmachine/bugsnag",
|
||||
"github.com/docker/machine/libmachine/cert",
|
||||
"github.com/docker/machine/libmachine/check",
|
||||
"github.com/docker/machine/libmachine/drivers",
|
||||
"github.com/docker/machine/libmachine/drivers/plugin",
|
||||
"github.com/docker/machine/libmachine/drivers/plugin/localbinary",
|
||||
"github.com/docker/machine/libmachine/drivers/rpc",
|
||||
"github.com/docker/machine/libmachine/engine",
|
||||
"github.com/docker/machine/libmachine/examples",
|
||||
"github.com/docker/machine/libmachine/host",
|
||||
"github.com/docker/machine/libmachine/hosttest",
|
||||
"github.com/docker/machine/libmachine/libmachinetest",
|
||||
"github.com/docker/machine/libmachine/log",
|
||||
"github.com/docker/machine/libmachine/mcndockerclient",
|
||||
"github.com/docker/machine/libmachine/mcnerror",
|
||||
"github.com/docker/machine/libmachine/mcnflag",
|
||||
"github.com/docker/machine/libmachine/mcnutils",
|
||||
"github.com/docker/machine/libmachine/persist",
|
||||
"github.com/docker/machine/libmachine/persist/persisttest",
|
||||
"github.com/docker/machine/libmachine/provider",
|
||||
"github.com/docker/machine/libmachine/provision",
|
||||
"github.com/docker/machine/libmachine/provision/pkgaction",
|
||||
"github.com/docker/machine/libmachine/provision/serviceaction",
|
||||
"github.com/docker/machine/libmachine/ssh",
|
||||
"github.com/docker/machine/libmachine/state",
|
||||
"github.com/docker/machine/libmachine/swarm",
|
||||
"github.com/docker/machine/libmachine/version",
|
||||
"github.com/docker/machine/version"
|
||||
],
|
||||
"GoVersion": "go1.4.2",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/bugsnag/bugsnag-go",
|
||||
"Comment": "v1.0.5-19-g02e9528",
|
||||
"Rev": "02e952891c52fbcb15f113d90633897355783b6e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/bugsnag/osext",
|
||||
"Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/bugsnag/panicwrap",
|
||||
"Rev": "e5f9854865b9778a45169fc249e99e338d4d6f27"
|
||||
"ImportPath": "code.google.com/p/goauth2/oauth",
|
||||
"Comment": "weekly-56",
|
||||
"Rev": "afe77d958c701557ec5dc56f6936fcc194d15520"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go",
|
||||
"Comment": "v1.1-17-g515f3ec",
|
||||
"Rev": "515f3ec74ce6a5b31e934cefae997c97bd0a1b1e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/aws",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
|
||||
"Comment": "v1.0.2",
|
||||
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cenkalti/backoff",
|
||||
"Rev": "9831e1e25c874e0a0601b6dc43641071414eec7a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
"Comment": "1.2.0-179-g0302d39",
|
||||
"Rev": "0302d3914d2a6ad61404584cdae6e6dbc9c03599"
|
||||
"Comment": "1.2.0-64-ge1712f3",
|
||||
"Rev": "e1712f381785e32046927f64a7c86fe569203196"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/digitalocean/godo",
|
||||
"Comment": "v0.9.0-8-g2124bf3",
|
||||
"Rev": "2124bf3eeeb4ac070337bb19ef7b76a745de56f4"
|
||||
"Comment": "v0.5.0",
|
||||
"Rev": "5478aae80694de1d2d0e02c386bbedd201266234"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/dockerversion",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/engine",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/archive",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/fileutils",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/ioutils",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/mflag",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/parsers",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/pools",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/promise",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/system",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/term",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/timeutils",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/pkg/units",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-ini/ini",
|
||||
"Comment": "v0-56-g03e0e7d",
|
||||
"Rev": "03e0e7d51a13a91c765d8d0161246bc14a38001a"
|
||||
"ImportPath": "github.com/docker/docker/pkg/version",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
||||
"Comment": "v1.5.0",
|
||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/libtrust",
|
||||
"Rev": "c54fbb67c1f1e68d7d6f8d2ad7c9360404616a41"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/go-querystring/query",
|
||||
"Rev": "30f7a39f4a218feb5325f3aebc60c32a572a8274"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jmespath/go-jmespath",
|
||||
"Comment": "0.2.2",
|
||||
"Rev": "3433f3ea46d9f8019119e7dd41274e112a2359a9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/mapstructure",
|
||||
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pyr/egoscale/src/egoscale",
|
||||
"Rev": "bbaa67324aeeacc90430c1fe0a9c620d3929512e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud",
|
||||
"Comment": "v1.0.0-558-gce0f487",
|
||||
"Comment": "v1.0.0-558-ce0f487",
|
||||
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/samalba/dockerclient",
|
||||
"Rev": "f661dd4754aa5c52da85d04b5871ee0e11f4b59c"
|
||||
"ImportPath": "github.com/skarademir/naturalsort",
|
||||
"Rev": "983d4d86054d80f91fd04dd62ec52c1d078ce403"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/skarademir/naturalsort",
|
||||
"Rev": "69a5d87bef620f77ee8508db30c846b3b84b111e"
|
||||
"ImportPath": "github.com/smartystreets/go-aws-auth",
|
||||
"Rev": "1f0db8c0ee6362470abe06a94e3385927ed72a4b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/testify/assert",
|
||||
"Rev": "e4ec8152c15fc46bd5056ce65997a07c7d415325"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pyr/egoscale/src/egoscale",
|
||||
"Rev": "bbaa67324aeeacc90430c1fe0a9c620d3929512e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/tent/http-link-go",
|
||||
"Rev": "ac974c61c2f990f4115b119354b5e0b47550e888"
|
||||
@@ -192,55 +143,17 @@
|
||||
"Comment": "v0.0.2",
|
||||
"Rev": "66a23eaabc61518f91769939ff541886fe1dceef"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/curve25519",
|
||||
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ssh",
|
||||
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "4f2fc6c1e69d41baf187332ee08fbd2b296f21ed"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
"Rev": "442624c9ec9243441e83b374a9e22ac549b5c51d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys/windows/registry",
|
||||
"Rev": "d9157a9621b69ad1d8d77a1933590c416593f24f"
|
||||
"Rev": "1fbbd62cfec66bd39d91e97749579579d4d3037e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/compute/v1",
|
||||
"Rev": "030d584ade5f79aa2ed0ce067e8f7da50c9a10d5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/gensupport",
|
||||
"Rev": "030d584ade5f79aa2ed0ce067e8f7da50c9a10d5"
|
||||
"Rev": "aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/api/googleapi",
|
||||
"Rev": "030d584ade5f79aa2ed0ce067e8f7da50c9a10d5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/cloud/compute/metadata",
|
||||
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/cloud/internal",
|
||||
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vmware/govmomi",
|
||||
"Comment": "prerelease-v0.1.0-73-gfc131d4-65-g482cd82",
|
||||
"Rev": "482cd823716e0fa9bc4e186d262a6ea23d940fbf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/go-units",
|
||||
"Comment": "v0.1.0-21-g0bbddae",
|
||||
"Rev": "0bbddae09c5a5419a8c6dcdd7ff90da3d450393b"
|
||||
"Rev": "aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/pkg
|
||||
/bin
|
||||
100
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go
generated
vendored
Normal file
100
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2011 The goauth2 Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This program makes a call to the specified API, authenticated with OAuth2.
|
||||
// a list of example APIs can be found at https://code.google.com/oauthplayground/
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"code.google.com/p/goauth2/oauth"
|
||||
)
|
||||
|
||||
var (
|
||||
clientId = flag.String("id", "", "Client ID")
|
||||
clientSecret = flag.String("secret", "", "Client Secret")
|
||||
scope = flag.String("scope", "https://www.googleapis.com/auth/userinfo.profile", "OAuth scope")
|
||||
redirectURL = flag.String("redirect_url", "oob", "Redirect URL")
|
||||
authURL = flag.String("auth_url", "https://accounts.google.com/o/oauth2/auth", "Authentication URL")
|
||||
tokenURL = flag.String("token_url", "https://accounts.google.com/o/oauth2/token", "Token URL")
|
||||
requestURL = flag.String("request_url", "https://www.googleapis.com/oauth2/v1/userinfo", "API request")
|
||||
code = flag.String("code", "", "Authorization Code")
|
||||
cachefile = flag.String("cache", "cache.json", "Token cache file")
|
||||
)
|
||||
|
||||
const usageMsg = `
|
||||
To obtain a request token you must specify both -id and -secret.
|
||||
|
||||
To obtain Client ID and Secret, see the "OAuth 2 Credentials" section under
|
||||
the "API Access" tab on this page: https://code.google.com/apis/console/
|
||||
|
||||
Once you have completed the OAuth flow, the credentials should be stored inside
|
||||
the file specified by -cache and you may run without the -id and -secret flags.
|
||||
`
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Set up a configuration.
|
||||
config := &oauth.Config{
|
||||
ClientId: *clientId,
|
||||
ClientSecret: *clientSecret,
|
||||
RedirectURL: *redirectURL,
|
||||
Scope: *scope,
|
||||
AuthURL: *authURL,
|
||||
TokenURL: *tokenURL,
|
||||
TokenCache: oauth.CacheFile(*cachefile),
|
||||
}
|
||||
|
||||
// Set up a Transport using the config.
|
||||
transport := &oauth.Transport{Config: config}
|
||||
|
||||
// Try to pull the token from the cache; if this fails, we need to get one.
|
||||
token, err := config.TokenCache.Token()
|
||||
if err != nil {
|
||||
if *clientId == "" || *clientSecret == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprint(os.Stderr, usageMsg)
|
||||
os.Exit(2)
|
||||
}
|
||||
if *code == "" {
|
||||
// Get an authorization code from the data provider.
|
||||
// ("Please ask the user if I can access this resource.")
|
||||
url := config.AuthCodeURL("")
|
||||
fmt.Print("Visit this URL to get a code, then run again with -code=YOUR_CODE\n\n")
|
||||
fmt.Println(url)
|
||||
return
|
||||
}
|
||||
// Exchange the authorization code for an access token.
|
||||
// ("Here's the code you gave the user, now give me a token!")
|
||||
token, err = transport.Exchange(*code)
|
||||
if err != nil {
|
||||
log.Fatal("Exchange:", err)
|
||||
}
|
||||
// (The Exchange method will automatically cache the token.)
|
||||
fmt.Printf("Token is cached in %v\n", config.TokenCache)
|
||||
}
|
||||
|
||||
// Make the actual request using the cached token to authenticate.
|
||||
// ("Here's the token, let me in!")
|
||||
transport.Token = token
|
||||
|
||||
// Make the request.
|
||||
r, err := transport.Client().Get(*requestURL)
|
||||
if err != nil {
|
||||
log.Fatal("Get:", err)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
// Write the response to standard output.
|
||||
io.Copy(os.Stdout, r.Body)
|
||||
|
||||
// Send final carriage return, just to be neat.
|
||||
fmt.Println()
|
||||
}
|
||||
1
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/example.client_secrets.json
generated
vendored
Normal file
1
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/example.client_secrets.json
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"XXXXXXXXXXXX@developer.gserviceaccount.com","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/XXXXXXXXXXXX@developer.gserviceaccount.com","client_id":"XXXXXXXXXXXX.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}
|
||||
20
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/example.pem
generated
vendored
Normal file
20
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/example.pem
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Bag Attributes
|
||||
friendlyName: privatekey
|
||||
localKeyID: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Key Attributes: <No Attributes>
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
XXXXxyXXXXXXXxxyxxxX9y0XXYXXXXYXXxXyxxXxXxXXXyXXXXx4yx1xy1xyYxxY
|
||||
1XxYy38YxXxxxyXxyyxx+xxxxyx1Y1xYx7yx2/Y1XyyXYYYxY5YXxX0xY/Y642yX
|
||||
zYYxYXzXYxY0Y8y9YxyYXxxX40YyXxxXX4XXxx7XxXxxXyXxYYXxXyxX5XY0Yy2X
|
||||
1YX0XXyy6YXyXx9XxXxyXX9XXYXxXxXXXXXXxYXYY3Y8Yy311XYYY81XyY14Xyyx
|
||||
xXyx7xxXXXxxxxyyyX4YYYXyYyYXyxX4XYXYyxXYyx9xy23xXYyXyxYxXxx1XXXY
|
||||
y98yX6yYxyyyX4Xyx1Xy/0yxxYxXxYYx2xx7yYXXXxYXXXxyXyyYYxx5XX2xxyxy
|
||||
y6Yyyx0XX3YYYyx9YYXXXX7y0yxXXy+90XYz1y2xyx7yXxX+8X0xYxXXYxxyxYYy
|
||||
YXx8Yy4yX0Xyxxx6yYX92yxy1YYYzyyyyxy55x/yyXXXYYXYXXzXXxYYxyXY8XXX
|
||||
+y9+yXxX7XxxyYYxxXYxyY623xxXxYX59x5Y6yYyXYY4YxXXYXXXYxXYxXxXXx6x
|
||||
YXX7XxXX2X0XY7YXyYy1XXxYXxXxYY1xXXxxxyy+07zXYxYxxXyyxxyxXx1XYy5X
|
||||
5XYzyxYxXXYyX9XX7xX8xXxx+XXYyYXXXX5YY1x8Yxyx54Xy/1XXyyYXY5YxYyxY
|
||||
XyyxXyX/YxxXXXxXXYXxyxx63xX/xxyYXXyYzx0XY+YxX5xyYyyxxxXXYX/94XXy
|
||||
Xx63xYxXyXY3/XXxyyXX15XXXyz08XYY5YYXY/YXy/96x68XyyXXxYyXy4xYXx5x
|
||||
7yxxyxxYxXxyx3y=
|
||||
-----END PRIVATE KEY-----
|
||||
114
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/main.go
generated
vendored
Normal file
114
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/main.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2011 The goauth2 Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This program makes a read only call to the Google Cloud Storage API,
|
||||
// authenticated with OAuth2. A list of example APIs can be found at
|
||||
// https://code.google.com/oauthplayground/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/goauth2/oauth/jwt"
|
||||
)
|
||||
|
||||
const scope = "https://www.googleapis.com/auth/devstorage.read_only"
|
||||
|
||||
var (
|
||||
secretsFile = flag.String("s", "", "JSON encoded secrets for the service account")
|
||||
pemFile = flag.String("k", "", "private pem key file for the service account")
|
||||
)
|
||||
|
||||
const usageMsg = `
|
||||
You must specify -k and -s.
|
||||
|
||||
To obtain client secrets and pem, see the "OAuth 2 Credentials" section under
|
||||
the "API Access" tab on this page: https://code.google.com/apis/console/
|
||||
|
||||
Google Cloud Storage must also be turned on in the API console.
|
||||
`
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *secretsFile == "" || *pemFile == "" {
|
||||
flag.Usage()
|
||||
fmt.Println(usageMsg)
|
||||
return
|
||||
}
|
||||
|
||||
// Read the secret file bytes into the config.
|
||||
secretBytes, err := ioutil.ReadFile(*secretsFile)
|
||||
if err != nil {
|
||||
log.Fatal("error reading secerets file:", err)
|
||||
}
|
||||
var config struct {
|
||||
Web struct {
|
||||
ClientEmail string `json:"client_email"`
|
||||
ClientID string `json:"client_id"`
|
||||
TokenURI string `json:"token_uri"`
|
||||
}
|
||||
}
|
||||
err = json.Unmarshal(secretBytes, &config)
|
||||
if err != nil {
|
||||
log.Fatal("error unmarshalling secerets:", err)
|
||||
}
|
||||
|
||||
// Get the project ID from the client ID.
|
||||
projectID := strings.SplitN(config.Web.ClientID, "-", 2)[0]
|
||||
|
||||
// Read the pem file bytes for the private key.
|
||||
keyBytes, err := ioutil.ReadFile(*pemFile)
|
||||
if err != nil {
|
||||
log.Fatal("error reading private key file:", err)
|
||||
}
|
||||
|
||||
// Craft the ClaimSet and JWT token.
|
||||
t := jwt.NewToken(config.Web.ClientEmail, scope, keyBytes)
|
||||
t.ClaimSet.Aud = config.Web.TokenURI
|
||||
|
||||
// We need to provide a client.
|
||||
c := &http.Client{}
|
||||
|
||||
// Get the access token.
|
||||
o, err := t.Assert(c)
|
||||
if err != nil {
|
||||
log.Fatal("assertion error:", err)
|
||||
}
|
||||
|
||||
// Refresh token will be missing, but this access_token will be good
|
||||
// for one hour.
|
||||
fmt.Printf("access_token = %v\n", o.AccessToken)
|
||||
fmt.Printf("refresh_token = %v\n", o.RefreshToken)
|
||||
fmt.Printf("expires %v\n", o.Expiry)
|
||||
|
||||
// Form the request to list Google Cloud Storage buckets.
|
||||
req, err := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
|
||||
if err != nil {
|
||||
log.Fatal("http.NewRequest:", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "OAuth "+o.AccessToken)
|
||||
req.Header.Set("x-goog-api-version", "2")
|
||||
req.Header.Set("x-goog-project-id", projectID)
|
||||
|
||||
// Make the request.
|
||||
r, err := c.Do(req)
|
||||
if err != nil {
|
||||
log.Fatal("API request error:", err)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
// Write the response to standard output.
|
||||
res, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Fatal("error reading API request results:", err)
|
||||
}
|
||||
fmt.Printf("\nRESULT:\n%s\n", res)
|
||||
}
|
||||
511
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt.go
generated
vendored
Normal file
511
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt.go
generated
vendored
Normal file
@@ -0,0 +1,511 @@
|
||||
// Copyright 2012 The goauth2 Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The jwt package provides support for creating credentials for OAuth2 service
|
||||
// account requests.
|
||||
//
|
||||
// For examples of the package usage please see jwt_test.go.
|
||||
// Example usage (error handling omitted for brevity):
|
||||
//
|
||||
// // Craft the ClaimSet and JWT token.
|
||||
// iss := "XXXXXXXXXXXX@developer.gserviceaccount.com"
|
||||
// scope := "https://www.googleapis.com/auth/devstorage.read_only"
|
||||
// t := jwt.NewToken(iss, scope, pemKeyBytes)
|
||||
//
|
||||
// // We need to provide a client.
|
||||
// c := &http.Client{}
|
||||
//
|
||||
// // Get the access token.
|
||||
// o, _ := t.Assert(c)
|
||||
//
|
||||
// // Form the request to the service.
|
||||
// req, _ := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
|
||||
// req.Header.Set("Authorization", "OAuth "+o.AccessToken)
|
||||
// req.Header.Set("x-goog-api-version", "2")
|
||||
// req.Header.Set("x-goog-project-id", "XXXXXXXXXXXX")
|
||||
//
|
||||
// // Make the request.
|
||||
// result, _ := c.Do(req)
|
||||
//
|
||||
// For info on OAuth2 service accounts please see the online documentation.
|
||||
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount
|
||||
//
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.google.com/p/goauth2/oauth"
|
||||
)
|
||||
|
||||
// These are the default/standard values for this to work for Google service accounts.
|
||||
const (
|
||||
stdAlgorithm = "RS256"
|
||||
stdType = "JWT"
|
||||
stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer"
|
||||
stdGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
stdAud = "https://accounts.google.com/o/oauth2/token"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidKey = errors.New("Invalid Key")
|
||||
)
|
||||
|
||||
// base64Encode returns and Base64url encoded version of the input string with any
|
||||
// trailing "=" stripped.
|
||||
func base64Encode(b []byte) string {
|
||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
||||
}
|
||||
|
||||
// base64Decode decodes the Base64url encoded string
|
||||
func base64Decode(s string) ([]byte, error) {
|
||||
// add back missing padding
|
||||
switch len(s) % 4 {
|
||||
case 2:
|
||||
s += "=="
|
||||
case 3:
|
||||
s += "="
|
||||
}
|
||||
return base64.URLEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// The JWT claim set contains information about the JWT including the
|
||||
// permissions being requested (scopes), the target of the token, the issuer,
|
||||
// the time the token was issued, and the lifetime of the token.
|
||||
//
|
||||
// Aud is usually https://accounts.google.com/o/oauth2/token
|
||||
type ClaimSet struct {
|
||||
Iss string `json:"iss"` // email address of the client_id of the application making the access token request
|
||||
Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests
|
||||
Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional).
|
||||
Prn string `json:"prn,omitempty"` // email for which the application is requesting delegated access (Optional).
|
||||
Exp int64 `json:"exp"`
|
||||
Iat int64 `json:"iat"`
|
||||
Typ string `json:"typ,omitempty"`
|
||||
Sub string `json:"sub,omitempty"` // Add support for googleapi delegation support
|
||||
|
||||
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
|
||||
// This array is marshalled using custom code (see (c *ClaimSet) encode()).
|
||||
PrivateClaims map[string]interface{} `json:"-"`
|
||||
|
||||
exp time.Time
|
||||
iat time.Time
|
||||
}
|
||||
|
||||
// setTimes sets iat and exp to time.Now() and iat.Add(time.Hour) respectively.
|
||||
//
|
||||
// Note that these times have nothing to do with the expiration time for the
|
||||
// access_token returned by the server. These have to do with the lifetime of
|
||||
// the encoded JWT.
|
||||
//
|
||||
// A JWT can be re-used for up to one hour after it was encoded. The access
|
||||
// token that is granted will also be good for one hour so there is little point
|
||||
// in trying to use the JWT a second time.
|
||||
func (c *ClaimSet) setTimes(t time.Time) {
|
||||
c.iat = t
|
||||
c.exp = c.iat.Add(time.Hour)
|
||||
}
|
||||
|
||||
var (
|
||||
jsonStart = []byte{'{'}
|
||||
jsonEnd = []byte{'}'}
|
||||
)
|
||||
|
||||
// encode returns the Base64url encoded form of the Signature.
|
||||
func (c *ClaimSet) encode() string {
|
||||
if c.exp.IsZero() || c.iat.IsZero() {
|
||||
c.setTimes(time.Now())
|
||||
}
|
||||
if c.Aud == "" {
|
||||
c.Aud = stdAud
|
||||
}
|
||||
c.Exp = c.exp.Unix()
|
||||
c.Iat = c.iat.Unix()
|
||||
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(c.PrivateClaims) == 0 {
|
||||
return base64Encode(b)
|
||||
}
|
||||
|
||||
// Marshal private claim set and then append it to b.
|
||||
prv, err := json.Marshal(c.PrivateClaims)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Invalid map of private claims %v", c.PrivateClaims))
|
||||
}
|
||||
|
||||
// Concatenate public and private claim JSON objects.
|
||||
if !bytes.HasSuffix(b, jsonEnd) {
|
||||
panic(fmt.Errorf("Invalid JSON %s", b))
|
||||
}
|
||||
if !bytes.HasPrefix(prv, jsonStart) {
|
||||
panic(fmt.Errorf("Invalid JSON %s", prv))
|
||||
}
|
||||
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
|
||||
b = append(b, prv[1:]...) // Append private claims.
|
||||
|
||||
return base64Encode(b)
|
||||
}
|
||||
|
||||
// Header describes the algorithm and type of token being generated,
|
||||
// and optionally a KeyID describing additional parameters for the
|
||||
// signature.
|
||||
type Header struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ"`
|
||||
KeyId string `json:"kid,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Header) encode() string {
|
||||
b, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return base64Encode(b)
|
||||
}
|
||||
|
||||
// A JWT is composed of three parts: a header, a claim set, and a signature.
|
||||
// The well formed and encoded JWT can then be exchanged for an access token.
|
||||
//
|
||||
// The Token is not a JWT, but is is encoded to produce a well formed JWT.
|
||||
//
|
||||
// When obtaining a key from the Google API console it will be downloaded in a
|
||||
// PKCS12 encoding. To use this key you will need to convert it to a PEM file.
|
||||
// This can be achieved with openssl.
|
||||
//
|
||||
// $ openssl pkcs12 -in <key.p12> -nocerts -passin pass:notasecret -nodes -out <key.pem>
|
||||
//
|
||||
// The contents of this file can then be used as the Key.
|
||||
type Token struct {
|
||||
ClaimSet *ClaimSet // claim set used to construct the JWT
|
||||
Header *Header // header used to construct the JWT
|
||||
Key []byte // PEM printable encoding of the private key
|
||||
pKey *rsa.PrivateKey
|
||||
|
||||
header string
|
||||
claim string
|
||||
sig string
|
||||
|
||||
useExternalSigner bool
|
||||
signer Signer
|
||||
}
|
||||
|
||||
// NewToken returns a filled in *Token based on the standard header,
|
||||
// and sets the Iat and Exp times based on when the call to Assert is
|
||||
// made.
|
||||
func NewToken(iss, scope string, key []byte) *Token {
|
||||
c := &ClaimSet{
|
||||
Iss: iss,
|
||||
Scope: scope,
|
||||
Aud: stdAud,
|
||||
}
|
||||
h := &Header{
|
||||
Algorithm: stdAlgorithm,
|
||||
Type: stdType,
|
||||
}
|
||||
t := &Token{
|
||||
ClaimSet: c,
|
||||
Header: h,
|
||||
Key: key,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Signer is an interface that given a JWT token, returns the header &
|
||||
// claim (serialized and urlEncoded to a byte slice), along with the
|
||||
// signature and an error (if any occured). It could modify any data
|
||||
// to sign (typically the KeyID).
|
||||
//
|
||||
// Example usage where a SHA256 hash of the original url-encoded token
|
||||
// with an added KeyID and secret data is used as a signature:
|
||||
//
|
||||
// var privateData = "secret data added to hash, indexed by KeyID"
|
||||
//
|
||||
// type SigningService struct{}
|
||||
//
|
||||
// func (ss *SigningService) Sign(in *jwt.Token) (newTokenData, sig []byte, err error) {
|
||||
// in.Header.KeyID = "signing service"
|
||||
// newTokenData = in.EncodeWithoutSignature()
|
||||
// dataToSign := fmt.Sprintf("%s.%s", newTokenData, privateData)
|
||||
// h := sha256.New()
|
||||
// _, err := h.Write([]byte(dataToSign))
|
||||
// sig = h.Sum(nil)
|
||||
// return
|
||||
// }
|
||||
type Signer interface {
|
||||
Sign(in *Token) (tokenData, signature []byte, err error)
|
||||
}
|
||||
|
||||
// NewSignerToken returns a *Token, using an external signer function
|
||||
func NewSignerToken(iss, scope string, signer Signer) *Token {
|
||||
t := NewToken(iss, scope, nil)
|
||||
t.useExternalSigner = true
|
||||
t.signer = signer
|
||||
return t
|
||||
}
|
||||
|
||||
// Expired returns a boolean value letting us know if the token has expired.
|
||||
func (t *Token) Expired() bool {
|
||||
return t.ClaimSet.exp.Before(time.Now())
|
||||
}
|
||||
|
||||
// Encode constructs and signs a Token returning a JWT ready to use for
|
||||
// requesting an access token.
|
||||
func (t *Token) Encode() (string, error) {
|
||||
var tok string
|
||||
t.header = t.Header.encode()
|
||||
t.claim = t.ClaimSet.encode()
|
||||
err := t.sign()
|
||||
if err != nil {
|
||||
return tok, err
|
||||
}
|
||||
tok = fmt.Sprintf("%s.%s.%s", t.header, t.claim, t.sig)
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// EncodeWithoutSignature returns the url-encoded value of the Token
|
||||
// before signing has occured (typically for use by external signers).
|
||||
func (t *Token) EncodeWithoutSignature() string {
|
||||
t.header = t.Header.encode()
|
||||
t.claim = t.ClaimSet.encode()
|
||||
return fmt.Sprintf("%s.%s", t.header, t.claim)
|
||||
}
|
||||
|
||||
// sign computes the signature for a Token. The details for this can be found
|
||||
// in the OAuth2 Service Account documentation.
|
||||
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature
|
||||
func (t *Token) sign() error {
|
||||
if t.useExternalSigner {
|
||||
fulldata, sig, err := t.signer.Sign(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
split := strings.Split(string(fulldata), ".")
|
||||
if len(split) != 2 {
|
||||
return errors.New("no token returned")
|
||||
}
|
||||
t.header = split[0]
|
||||
t.claim = split[1]
|
||||
t.sig = base64Encode(sig)
|
||||
return err
|
||||
}
|
||||
ss := fmt.Sprintf("%s.%s", t.header, t.claim)
|
||||
if t.pKey == nil {
|
||||
err := t.parsePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
h := sha256.New()
|
||||
h.Write([]byte(ss))
|
||||
b, err := rsa.SignPKCS1v15(rand.Reader, t.pKey, crypto.SHA256, h.Sum(nil))
|
||||
t.sig = base64Encode(b)
|
||||
return err
|
||||
}
|
||||
|
||||
// parsePrivateKey converts the Token's Key ([]byte) into a parsed
|
||||
// rsa.PrivateKey. If the key is not well formed this method will return an
|
||||
// ErrInvalidKey error.
|
||||
func (t *Token) parsePrivateKey() error {
|
||||
block, _ := pem.Decode(t.Key)
|
||||
if block == nil {
|
||||
return ErrInvalidKey
|
||||
}
|
||||
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var ok bool
|
||||
t.pKey, ok = parsedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return ErrInvalidKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Assert obtains an *oauth.Token from the remote server by encoding and sending
|
||||
// a JWT. The access_token will expire in one hour (3600 seconds) and cannot be
|
||||
// refreshed (no refresh_token is returned with the response). Once this token
|
||||
// expires call this method again to get a fresh one.
|
||||
func (t *Token) Assert(c *http.Client) (*oauth.Token, error) {
|
||||
var o *oauth.Token
|
||||
t.ClaimSet.setTimes(time.Now())
|
||||
u, v, err := t.buildRequest()
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
resp, err := c.PostForm(u, v)
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
o, err = handleResponse(resp)
|
||||
return o, err
|
||||
}
|
||||
|
||||
// buildRequest sets up the URL values and the proper URL string for making our
|
||||
// access_token request.
|
||||
func (t *Token) buildRequest() (string, url.Values, error) {
|
||||
v := url.Values{}
|
||||
j, err := t.Encode()
|
||||
if err != nil {
|
||||
return t.ClaimSet.Aud, v, err
|
||||
}
|
||||
v.Set("grant_type", stdGrantType)
|
||||
v.Set("assertion", j)
|
||||
return t.ClaimSet.Aud, v, nil
|
||||
}
|
||||
|
||||
// Used for decoding the response body.
|
||||
type respBody struct {
|
||||
IdToken string `json:"id_token"`
|
||||
Access string `json:"access_token"`
|
||||
Type string `json:"token_type"`
|
||||
ExpiresIn time.Duration `json:"expires_in"`
|
||||
}
|
||||
|
||||
// handleResponse returns a filled in *oauth.Token given the *http.Response from
|
||||
// a *http.Request created by buildRequest.
|
||||
func handleResponse(r *http.Response) (*oauth.Token, error) {
|
||||
o := &oauth.Token{}
|
||||
defer r.Body.Close()
|
||||
if r.StatusCode != 200 {
|
||||
return o, errors.New("invalid response: " + r.Status)
|
||||
}
|
||||
b := &respBody{}
|
||||
err := json.NewDecoder(r.Body).Decode(b)
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
o.AccessToken = b.Access
|
||||
if b.IdToken != "" {
|
||||
// decode returned id token to get expiry
|
||||
o.AccessToken = b.IdToken
|
||||
s := strings.Split(b.IdToken, ".")
|
||||
if len(s) < 2 {
|
||||
return nil, errors.New("invalid token received")
|
||||
}
|
||||
d, err := base64Decode(s[1])
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
c := &ClaimSet{}
|
||||
err = json.NewDecoder(bytes.NewBuffer(d)).Decode(c)
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
o.Expiry = time.Unix(c.Exp, 0)
|
||||
return o, nil
|
||||
}
|
||||
o.Expiry = time.Now().Add(b.ExpiresIn * time.Second)
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Transport implements http.RoundTripper. When configured with a valid
|
||||
// JWT and OAuth tokens it can be used to make authenticated HTTP requests.
|
||||
//
|
||||
// t := &jwt.Transport{jwtToken, oauthToken}
|
||||
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
|
||||
//
|
||||
// It will automatically refresh the OAuth token if it can, updating in place.
|
||||
type Transport struct {
|
||||
JWTToken *Token
|
||||
OAuthToken *oauth.Token
|
||||
|
||||
// Transport is the HTTP transport to use when making requests.
|
||||
// It will default to http.DefaultTransport if nil.
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// Creates a new authenticated transport.
|
||||
func NewTransport(token *Token) (*Transport, error) {
|
||||
oa, err := token.Assert(new(http.Client))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Transport{
|
||||
JWTToken: token,
|
||||
OAuthToken: oa,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client returns an *http.Client that makes OAuth-authenticated requests.
|
||||
func (t *Transport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
// Fetches the internal transport.
|
||||
func (t *Transport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// RoundTrip executes a single HTTP transaction using the Transport's
|
||||
// OAuthToken as authorization headers.
|
||||
//
|
||||
// This method will attempt to renew the token if it has expired and may return
|
||||
// an error related to that token renewal before attempting the client request.
|
||||
// If the token cannot be renewed a non-nil os.Error value will be returned.
|
||||
// If the token is invalid callers should expect HTTP-level errors,
|
||||
// as indicated by the Response's StatusCode.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// Sanity check the two tokens
|
||||
if t.JWTToken == nil {
|
||||
return nil, fmt.Errorf("no JWT token supplied")
|
||||
}
|
||||
if t.OAuthToken == nil {
|
||||
return nil, fmt.Errorf("no OAuth token supplied")
|
||||
}
|
||||
// Refresh the OAuth token if it has expired
|
||||
if t.OAuthToken.Expired() {
|
||||
if oa, err := t.JWTToken.Assert(new(http.Client)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
t.OAuthToken = oa
|
||||
}
|
||||
}
|
||||
// To set the Authorization header, we must make a copy of the Request
|
||||
// so that we don't modify the Request we were given.
|
||||
// This is required by the specification of http.RoundTripper.
|
||||
req = cloneRequest(req)
|
||||
req.Header.Set("Authorization", "Bearer "+t.OAuthToken.AccessToken)
|
||||
|
||||
// Make the HTTP request.
|
||||
return t.transport().RoundTrip(req)
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request.
|
||||
// The clone is a shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
// shallow copy of the struct
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
// deep copy of the Header
|
||||
r2.Header = make(http.Header)
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = s
|
||||
}
|
||||
return r2
|
||||
}
|
||||
486
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt_test.go
generated
vendored
Normal file
486
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt_test.go
generated
vendored
Normal file
@@ -0,0 +1,486 @@
|
||||
// Copyright 2012 The goauth2 Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// For package documentation please see jwt.go.
|
||||
//
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
stdHeaderStr = `{"alg":"RS256","typ":"JWT"}`
|
||||
iss = "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com"
|
||||
scope = "https://www.googleapis.com/auth/prediction"
|
||||
exp = 1328554385
|
||||
iat = 1328550785 // exp + 1 hour
|
||||
)
|
||||
|
||||
// Base64url encoded Header
|
||||
const headerEnc = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||
|
||||
// Base64url encoded ClaimSet
|
||||
const claimSetEnc = "eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ"
|
||||
|
||||
// Base64url encoded Signature
|
||||
const sigEnc = "olukbHreNiYrgiGCTEmY3eWGeTvYDSUHYoE84Jz3BRPBSaMdZMNOn_0CYK7UHPO7OdvUofjwft1dH59UxE9GWS02pjFti1uAQoImaqjLZoTXr8qiF6O_kDa9JNoykklWlRAIwGIZkDupCS-8cTAnM_ksSymiH1coKJrLDUX_BM0x2f4iMFQzhL5vT1ll-ZipJ0lNlxb5QsyXxDYcxtHYguF12-vpv3ItgT0STfcXoWzIGQoEbhwB9SBp9JYcQ8Ygz6pYDjm0rWX9LrchmTyDArCodpKLFtutNgcIFUP9fWxvwd1C2dNw5GjLcKr9a_SAERyoJ2WnCR1_j9N0wD2o0g"
|
||||
|
||||
// Base64url encoded Token
|
||||
const tokEnc = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ.olukbHreNiYrgiGCTEmY3eWGeTvYDSUHYoE84Jz3BRPBSaMdZMNOn_0CYK7UHPO7OdvUofjwft1dH59UxE9GWS02pjFti1uAQoImaqjLZoTXr8qiF6O_kDa9JNoykklWlRAIwGIZkDupCS-8cTAnM_ksSymiH1coKJrLDUX_BM0x2f4iMFQzhL5vT1ll-ZipJ0lNlxb5QsyXxDYcxtHYguF12-vpv3ItgT0STfcXoWzIGQoEbhwB9SBp9JYcQ8Ygz6pYDjm0rWX9LrchmTyDArCodpKLFtutNgcIFUP9fWxvwd1C2dNw5GjLcKr9a_SAERyoJ2WnCR1_j9N0wD2o0g"
|
||||
|
||||
// Private key for testing
|
||||
const privateKeyPem = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj
|
||||
7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/
|
||||
xmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs
|
||||
SliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18
|
||||
pe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk
|
||||
SBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk
|
||||
nQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq
|
||||
HD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y
|
||||
nHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9
|
||||
IisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2
|
||||
YCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU
|
||||
Z422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ
|
||||
vzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP
|
||||
B8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl
|
||||
aLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2
|
||||
eCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI
|
||||
aqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk
|
||||
klORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ
|
||||
CFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu
|
||||
UqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg
|
||||
soBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28
|
||||
bvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH
|
||||
504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL
|
||||
YXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx
|
||||
BeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
// Public key to go with the private key for testing
|
||||
const publicKeyPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV
|
||||
BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV
|
||||
MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM
|
||||
7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer
|
||||
uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp
|
||||
gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4
|
||||
+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3
|
||||
ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O
|
||||
gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh
|
||||
GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD
|
||||
AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr
|
||||
odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk
|
||||
+JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9
|
||||
ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql
|
||||
ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT
|
||||
cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
var (
|
||||
privateKeyPemBytes = []byte(privateKeyPem)
|
||||
publicKeyPemBytes = []byte(publicKeyPem)
|
||||
stdHeader = &Header{Algorithm: stdAlgorithm, Type: stdType}
|
||||
)
|
||||
|
||||
// Testing the urlEncode function.
|
||||
func TestUrlEncode(t *testing.T) {
|
||||
enc := base64Encode([]byte(stdHeaderStr))
|
||||
b := []byte(enc)
|
||||
if b[len(b)-1] == 61 {
|
||||
t.Error("TestUrlEncode: last chat == \"=\"")
|
||||
}
|
||||
if enc != headerEnc {
|
||||
t.Error("TestUrlEncode: enc != headerEnc")
|
||||
t.Errorf(" enc = %s", enc)
|
||||
t.Errorf(" headerEnc = %s", headerEnc)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the times are set properly.
|
||||
func TestClaimSetSetTimes(t *testing.T) {
|
||||
c := &ClaimSet{
|
||||
Iss: iss,
|
||||
Scope: scope,
|
||||
}
|
||||
iat := time.Unix(iat, 0)
|
||||
c.setTimes(iat)
|
||||
if c.exp.Unix() != exp {
|
||||
t.Error("TestClaimSetSetTimes: c.exp != exp")
|
||||
t.Errorf(" c.Exp = %d", c.exp.Unix())
|
||||
t.Errorf(" exp = %d", exp)
|
||||
}
|
||||
}
|
||||
|
||||
// Given a well formed ClaimSet, test for proper encoding.
|
||||
func TestClaimSetEncode(t *testing.T) {
|
||||
c := &ClaimSet{
|
||||
Iss: iss,
|
||||
Scope: scope,
|
||||
exp: time.Unix(exp, 0),
|
||||
iat: time.Unix(iat, 0),
|
||||
}
|
||||
enc := c.encode()
|
||||
re, err := base64Decode(enc)
|
||||
if err != nil {
|
||||
t.Fatalf("error decoding encoded claim set: %v", err)
|
||||
}
|
||||
|
||||
wa, err := base64Decode(claimSetEnc)
|
||||
if err != nil {
|
||||
t.Fatalf("error decoding encoded expected claim set: %v", err)
|
||||
}
|
||||
|
||||
if enc != claimSetEnc {
|
||||
t.Error("TestClaimSetEncode: enc != claimSetEnc")
|
||||
t.Errorf(" enc = %s", string(re))
|
||||
t.Errorf(" claimSetEnc = %s", string(wa))
|
||||
}
|
||||
}
|
||||
|
||||
// Test that claim sets with private claim names are encoded correctly.
|
||||
func TestClaimSetWithPrivateNameEncode(t *testing.T) {
|
||||
iatT := time.Unix(iat, 0)
|
||||
expT := time.Unix(exp, 0)
|
||||
|
||||
i, err := json.Marshal(iatT.Unix())
|
||||
if err != nil {
|
||||
t.Fatalf("error marshaling iatT value of %v: %v", iatT.Unix(), err)
|
||||
}
|
||||
iatStr := string(i)
|
||||
e, err := json.Marshal(expT.Unix())
|
||||
if err != nil {
|
||||
t.Fatalf("error marshaling expT value of %v: %v", expT.Unix(), err)
|
||||
}
|
||||
|
||||
expStr := string(e)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input map[string]interface{}
|
||||
want string
|
||||
}{
|
||||
// Test a simple int field.
|
||||
{
|
||||
"single simple field",
|
||||
map[string]interface{}{"amount": 22},
|
||||
`{` +
|
||||
`"iss":"` + iss + `",` +
|
||||
`"scope":"` + scope + `",` +
|
||||
`"aud":"` + stdAud + `",` +
|
||||
`"exp":` + expStr + `,` +
|
||||
`"iat":` + iatStr + `,` +
|
||||
`"amount":22` +
|
||||
`}`,
|
||||
},
|
||||
{
|
||||
"multiple simple fields",
|
||||
map[string]interface{}{"tracking_code": "axZf", "amount": 22},
|
||||
`{` +
|
||||
`"iss":"` + iss + `",` +
|
||||
`"scope":"` + scope + `",` +
|
||||
`"aud":"` + stdAud + `",` +
|
||||
`"exp":` + expStr + `,` +
|
||||
`"iat":` + iatStr + `,` +
|
||||
`"amount":22,` +
|
||||
`"tracking_code":"axZf"` +
|
||||
`}`,
|
||||
},
|
||||
{
|
||||
"nested struct fields",
|
||||
map[string]interface{}{
|
||||
"tracking_code": "axZf",
|
||||
"purchase": struct {
|
||||
Description string `json:"desc"`
|
||||
Quantity int32 `json:"q"`
|
||||
Time int64 `json:"t"`
|
||||
}{
|
||||
"toaster",
|
||||
5,
|
||||
iat,
|
||||
},
|
||||
},
|
||||
`{` +
|
||||
`"iss":"` + iss + `",` +
|
||||
`"scope":"` + scope + `",` +
|
||||
`"aud":"` + stdAud + `",` +
|
||||
`"exp":` + expStr + `,` +
|
||||
`"iat":` + iatStr + `,` +
|
||||
`"purchase":{"desc":"toaster","q":5,"t":` + iatStr + `},` +
|
||||
`"tracking_code":"axZf"` +
|
||||
`}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
c := &ClaimSet{
|
||||
Iss: iss,
|
||||
Scope: scope,
|
||||
Aud: stdAud,
|
||||
iat: iatT,
|
||||
exp: expT,
|
||||
PrivateClaims: testCase.input,
|
||||
}
|
||||
cJSON, err := base64Decode(c.encode())
|
||||
if err != nil {
|
||||
t.Fatalf("error decoding claim set: %v", err)
|
||||
}
|
||||
if string(cJSON) != testCase.want {
|
||||
t.Errorf("TestClaimSetWithPrivateNameEncode: enc != want in case %s", testCase.desc)
|
||||
t.Errorf(" enc = %s", cJSON)
|
||||
t.Errorf(" want = %s", testCase.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test the NewToken constructor.
|
||||
func TestNewToken(t *testing.T) {
|
||||
tok := NewToken(iss, scope, privateKeyPemBytes)
|
||||
if tok.ClaimSet.Iss != iss {
|
||||
t.Error("TestNewToken: tok.ClaimSet.Iss != iss")
|
||||
t.Errorf(" tok.ClaimSet.Iss = %s", tok.ClaimSet.Iss)
|
||||
t.Errorf(" iss = %s", iss)
|
||||
}
|
||||
if tok.ClaimSet.Scope != scope {
|
||||
t.Error("TestNewToken: tok.ClaimSet.Scope != scope")
|
||||
t.Errorf(" tok.ClaimSet.Scope = %s", tok.ClaimSet.Scope)
|
||||
t.Errorf(" scope = %s", scope)
|
||||
}
|
||||
if tok.ClaimSet.Aud != stdAud {
|
||||
t.Error("TestNewToken: tok.ClaimSet.Aud != stdAud")
|
||||
t.Errorf(" tok.ClaimSet.Aud = %s", tok.ClaimSet.Aud)
|
||||
t.Errorf(" stdAud = %s", stdAud)
|
||||
}
|
||||
if !bytes.Equal(tok.Key, privateKeyPemBytes) {
|
||||
t.Error("TestNewToken: tok.Key != privateKeyPemBytes")
|
||||
t.Errorf(" tok.Key = %s", tok.Key)
|
||||
t.Errorf(" privateKeyPemBytes = %s", privateKeyPemBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the private key parsing functions work.
|
||||
func TestParsePrivateKey(t *testing.T) {
|
||||
tok := &Token{
|
||||
Key: privateKeyPemBytes,
|
||||
}
|
||||
err := tok.parsePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("TestParsePrivateKey:tok.parsePrivateKey: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the token signature generated matches the golden standard.
|
||||
func TestTokenSign(t *testing.T) {
|
||||
tok := &Token{
|
||||
Key: privateKeyPemBytes,
|
||||
claim: claimSetEnc,
|
||||
header: headerEnc,
|
||||
}
|
||||
err := tok.parsePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("TestTokenSign:tok.parsePrivateKey: %v", err)
|
||||
}
|
||||
err = tok.sign()
|
||||
if err != nil {
|
||||
t.Errorf("TestTokenSign:tok.sign: %v", err)
|
||||
}
|
||||
if tok.sig != sigEnc {
|
||||
t.Error("TestTokenSign: tok.sig != sigEnc")
|
||||
t.Errorf(" tok.sig = %s", tok.sig)
|
||||
t.Errorf(" sigEnc = %s", sigEnc)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the token expiration function is working.
|
||||
func TestTokenExpired(t *testing.T) {
|
||||
c := &ClaimSet{}
|
||||
tok := &Token{
|
||||
ClaimSet: c,
|
||||
}
|
||||
now := time.Now()
|
||||
c.setTimes(now)
|
||||
if tok.Expired() != false {
|
||||
t.Error("TestTokenExpired: tok.Expired != false")
|
||||
}
|
||||
// Set the times as if they were set 2 hours ago.
|
||||
c.setTimes(now.Add(-2 * time.Hour))
|
||||
if tok.Expired() != true {
|
||||
t.Error("TestTokenExpired: tok.Expired != true")
|
||||
}
|
||||
}
|
||||
|
||||
// Given a well formed Token, test for proper encoding.
|
||||
func TestTokenEncode(t *testing.T) {
|
||||
c := &ClaimSet{
|
||||
Iss: iss,
|
||||
Scope: scope,
|
||||
exp: time.Unix(exp, 0),
|
||||
iat: time.Unix(iat, 0),
|
||||
}
|
||||
tok := &Token{
|
||||
ClaimSet: c,
|
||||
Header: stdHeader,
|
||||
Key: privateKeyPemBytes,
|
||||
}
|
||||
enc, err := tok.Encode()
|
||||
if err != nil {
|
||||
t.Errorf("TestTokenEncode:tok.Assertion: %v", err)
|
||||
}
|
||||
if enc != tokEnc {
|
||||
t.Error("TestTokenEncode: enc != tokEnc")
|
||||
t.Errorf(" enc = %s", enc)
|
||||
t.Errorf(" tokEnc = %s", tokEnc)
|
||||
}
|
||||
}
|
||||
|
||||
// Given a well formed Token we should get back a well formed request.
|
||||
func TestBuildRequest(t *testing.T) {
|
||||
c := &ClaimSet{
|
||||
Iss: iss,
|
||||
Scope: scope,
|
||||
exp: time.Unix(exp, 0),
|
||||
iat: time.Unix(iat, 0),
|
||||
}
|
||||
tok := &Token{
|
||||
ClaimSet: c,
|
||||
Header: stdHeader,
|
||||
Key: privateKeyPemBytes,
|
||||
}
|
||||
u, v, err := tok.buildRequest()
|
||||
if err != nil {
|
||||
t.Errorf("TestBuildRequest:BuildRequest: %v", err)
|
||||
}
|
||||
if u != c.Aud {
|
||||
t.Error("TestBuildRequest: u != c.Aud")
|
||||
t.Errorf(" u = %s", u)
|
||||
t.Errorf(" c.Aud = %s", c.Aud)
|
||||
}
|
||||
if v.Get("grant_type") != stdGrantType {
|
||||
t.Error("TestBuildRequest: grant_type != stdGrantType")
|
||||
t.Errorf(" grant_type = %s", v.Get("grant_type"))
|
||||
t.Errorf(" stdGrantType = %s", stdGrantType)
|
||||
}
|
||||
if v.Get("assertion") != tokEnc {
|
||||
t.Error("TestBuildRequest: assertion != tokEnc")
|
||||
t.Errorf(" assertion = %s", v.Get("assertion"))
|
||||
t.Errorf(" tokEnc = %s", tokEnc)
|
||||
}
|
||||
}
|
||||
|
||||
// Given a well formed access request response we should get back a oauth.Token.
|
||||
func TestHandleResponse(t *testing.T) {
|
||||
rb := &respBody{
|
||||
Access: "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
|
||||
Type: "Bearer",
|
||||
ExpiresIn: 3600,
|
||||
}
|
||||
b, err := json.Marshal(rb)
|
||||
if err != nil {
|
||||
t.Errorf("TestHandleResponse:json.Marshal: %v", err)
|
||||
}
|
||||
r := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewReader(b)),
|
||||
}
|
||||
o, err := handleResponse(r)
|
||||
if err != nil {
|
||||
t.Errorf("TestHandleResponse:handleResponse: %v", err)
|
||||
}
|
||||
if o.AccessToken != rb.Access {
|
||||
t.Error("TestHandleResponse: o.AccessToken != rb.Access")
|
||||
t.Errorf(" o.AccessToken = %s", o.AccessToken)
|
||||
t.Errorf(" rb.Access = %s", rb.Access)
|
||||
}
|
||||
if o.Expired() {
|
||||
t.Error("TestHandleResponse: o.Expired == true")
|
||||
}
|
||||
}
|
||||
|
||||
// passthrough signature for test
|
||||
type FakeSigner struct{}
|
||||
|
||||
func (f FakeSigner) Sign(tok *Token) ([]byte, []byte, error) {
|
||||
block, _ := pem.Decode(privateKeyPemBytes)
|
||||
pKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
ss := headerEnc + "." + claimSetEnc
|
||||
h := sha256.New()
|
||||
h.Write([]byte(ss))
|
||||
b, _ := rsa.SignPKCS1v15(rand.Reader, pKey, crypto.SHA256, h.Sum(nil))
|
||||
return []byte(ss), b, nil
|
||||
}
|
||||
|
||||
// Given an external signer, get back a valid and signed JWT
|
||||
func TestExternalSigner(t *testing.T) {
|
||||
tok := NewSignerToken(iss, scope, FakeSigner{})
|
||||
enc, _ := tok.Encode()
|
||||
if enc != tokEnc {
|
||||
t.Errorf("TestExternalSigner: enc != tokEnc")
|
||||
t.Errorf(" enc = %s", enc)
|
||||
t.Errorf(" tokEnc = %s", tokEnc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleResponseWithNewExpiry(t *testing.T) {
|
||||
rb := &respBody{
|
||||
IdToken: tokEnc,
|
||||
}
|
||||
b, err := json.Marshal(rb)
|
||||
if err != nil {
|
||||
t.Errorf("TestHandleResponse:json.Marshal: %v", err)
|
||||
}
|
||||
r := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewReader(b)),
|
||||
}
|
||||
o, err := handleResponse(r)
|
||||
if err != nil {
|
||||
t.Errorf("TestHandleResponse:handleResponse: %v", err)
|
||||
}
|
||||
if o.Expiry != time.Unix(exp, 0) {
|
||||
t.Error("TestHandleResponse: o.Expiry != exp")
|
||||
t.Errorf(" o.Expiry = %s", o.Expiry)
|
||||
t.Errorf(" exp = %s", time.Unix(exp, 0))
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder for future Assert tests.
|
||||
func TestAssert(t *testing.T) {
|
||||
// Since this method makes a call to BuildRequest, an htttp.Client, and
|
||||
// finally HandleResponse there is not much more to test. This is here
|
||||
// as a placeholder if that changes.
|
||||
}
|
||||
|
||||
// Benchmark for the end-to-end encoding of a well formed token.
|
||||
func BenchmarkTokenEncode(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c := &ClaimSet{
|
||||
Iss: iss,
|
||||
Scope: scope,
|
||||
exp: time.Unix(exp, 0),
|
||||
iat: time.Unix(iat, 0),
|
||||
}
|
||||
tok := &Token{
|
||||
ClaimSet: c,
|
||||
Key: privateKeyPemBytes,
|
||||
}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tok.Encode()
|
||||
}
|
||||
}
|
||||
476
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go
generated
vendored
Normal file
476
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go
generated
vendored
Normal file
@@ -0,0 +1,476 @@
|
||||
// Copyright 2011 The goauth2 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 oauth supports making OAuth2-authenticated HTTP requests.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// // Specify your configuration. (typically as a global variable)
|
||||
// var config = &oauth.Config{
|
||||
// ClientId: YOUR_CLIENT_ID,
|
||||
// ClientSecret: YOUR_CLIENT_SECRET,
|
||||
// Scope: "https://www.googleapis.com/auth/buzz",
|
||||
// AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||
// TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||
// RedirectURL: "http://you.example.org/handler",
|
||||
// }
|
||||
//
|
||||
// // A landing page redirects to the OAuth provider to get the auth code.
|
||||
// func landing(w http.ResponseWriter, r *http.Request) {
|
||||
// http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
|
||||
// }
|
||||
//
|
||||
// // The user will be redirected back to this handler, that takes the
|
||||
// // "code" query parameter and Exchanges it for an access token.
|
||||
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||
// t := &oauth.Transport{Config: config}
|
||||
// t.Exchange(r.FormValue("code"))
|
||||
// // The Transport now has a valid Token. Create an *http.Client
|
||||
// // with which we can make authenticated API requests.
|
||||
// c := t.Client()
|
||||
// c.Post(...)
|
||||
// // ...
|
||||
// // btw, r.FormValue("state") == "foo"
|
||||
// }
|
||||
//
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OAuthError is the error type returned by many operations.
|
||||
//
|
||||
// In retrospect it should not exist. Don't depend on it.
|
||||
type OAuthError struct {
|
||||
prefix string
|
||||
msg string
|
||||
}
|
||||
|
||||
func (oe OAuthError) Error() string {
|
||||
return "OAuthError: " + oe.prefix + ": " + oe.msg
|
||||
}
|
||||
|
||||
// Cache specifies the methods that implement a Token cache.
|
||||
type Cache interface {
|
||||
Token() (*Token, error)
|
||||
PutToken(*Token) error
|
||||
}
|
||||
|
||||
// CacheFile implements Cache. Its value is the name of the file in which
|
||||
// the Token is stored in JSON format.
|
||||
type CacheFile string
|
||||
|
||||
func (f CacheFile) Token() (*Token, error) {
|
||||
file, err := os.Open(string(f))
|
||||
if err != nil {
|
||||
return nil, OAuthError{"CacheFile.Token", err.Error()}
|
||||
}
|
||||
defer file.Close()
|
||||
tok := &Token{}
|
||||
if err := json.NewDecoder(file).Decode(tok); err != nil {
|
||||
return nil, OAuthError{"CacheFile.Token", err.Error()}
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
func (f CacheFile) PutToken(tok *Token) error {
|
||||
file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
||||
}
|
||||
if err := json.NewEncoder(file).Encode(tok); err != nil {
|
||||
file.Close()
|
||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config is the configuration of an OAuth consumer.
|
||||
type Config struct {
|
||||
// ClientId is the OAuth client identifier used when communicating with
|
||||
// the configured OAuth provider.
|
||||
ClientId string
|
||||
|
||||
// ClientSecret is the OAuth client secret used when communicating with
|
||||
// the configured OAuth provider.
|
||||
ClientSecret string
|
||||
|
||||
// Scope identifies the level of access being requested. Multiple scope
|
||||
// values should be provided as a space-delimited string.
|
||||
Scope string
|
||||
|
||||
// AuthURL is the URL the user will be directed to in order to grant
|
||||
// access.
|
||||
AuthURL string
|
||||
|
||||
// TokenURL is the URL used to retrieve OAuth tokens.
|
||||
TokenURL string
|
||||
|
||||
// RedirectURL is the URL to which the user will be returned after
|
||||
// granting (or denying) access.
|
||||
RedirectURL string
|
||||
|
||||
// TokenCache allows tokens to be cached for subsequent requests.
|
||||
TokenCache Cache
|
||||
|
||||
// AccessType is an OAuth extension that gets sent as the
|
||||
// "access_type" field in the URL from AuthCodeURL.
|
||||
// See https://developers.google.com/accounts/docs/OAuth2WebServer.
|
||||
// It may be "online" (the default) or "offline".
|
||||
// If your application needs to refresh access tokens when the
|
||||
// user is not present at the browser, then use offline. This
|
||||
// will result in your application obtaining a refresh token
|
||||
// the first time your application exchanges an authorization
|
||||
// code for a user.
|
||||
AccessType string
|
||||
|
||||
// ApprovalPrompt indicates whether the user should be
|
||||
// re-prompted for consent. If set to "auto" (default) the
|
||||
// user will be prompted only if they haven't previously
|
||||
// granted consent and the code can only be exchanged for an
|
||||
// access token.
|
||||
// If set to "force" the user will always be prompted, and the
|
||||
// code can be exchanged for a refresh token.
|
||||
ApprovalPrompt string
|
||||
}
|
||||
|
||||
// Token contains an end-user's tokens.
|
||||
// This is the data you must store to persist authentication.
|
||||
type Token struct {
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
Expiry time.Time // If zero the token has no (known) expiry time.
|
||||
|
||||
// Extra optionally contains extra metadata from the server
|
||||
// when updating a token. The only current key that may be
|
||||
// populated is "id_token". It may be nil and will be
|
||||
// initialized as needed.
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
// Expired reports whether the token has expired or is invalid.
|
||||
func (t *Token) Expired() bool {
|
||||
if t.AccessToken == "" {
|
||||
return true
|
||||
}
|
||||
if t.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
return t.Expiry.Before(time.Now())
|
||||
}
|
||||
|
||||
// Transport implements http.RoundTripper. When configured with a valid
|
||||
// Config and Token it can be used to make authenticated HTTP requests.
|
||||
//
|
||||
// t := &oauth.Transport{config}
|
||||
// t.Exchange(code)
|
||||
// // t now contains a valid Token
|
||||
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
|
||||
//
|
||||
// It will automatically refresh the Token if it can,
|
||||
// updating the supplied Token in place.
|
||||
type Transport struct {
|
||||
*Config
|
||||
*Token
|
||||
|
||||
// mu guards modifying the token.
|
||||
mu sync.Mutex
|
||||
|
||||
// Transport is the HTTP transport to use when making requests.
|
||||
// It will default to http.DefaultTransport if nil.
|
||||
// (It should never be an oauth.Transport.)
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// Client returns an *http.Client that makes OAuth-authenticated requests.
|
||||
func (t *Transport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
func (t *Transport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// AuthCodeURL returns a URL that the end-user should be redirected to,
|
||||
// so that they may obtain an authorization code.
|
||||
func (c *Config) AuthCodeURL(state string) string {
|
||||
url_, err := url.Parse(c.AuthURL)
|
||||
if err != nil {
|
||||
panic("AuthURL malformed: " + err.Error())
|
||||
}
|
||||
q := url.Values{
|
||||
"response_type": {"code"},
|
||||
"client_id": {c.ClientId},
|
||||
"state": condVal(state),
|
||||
"scope": condVal(c.Scope),
|
||||
"redirect_uri": condVal(c.RedirectURL),
|
||||
"access_type": condVal(c.AccessType),
|
||||
"approval_prompt": condVal(c.ApprovalPrompt),
|
||||
}.Encode()
|
||||
if url_.RawQuery == "" {
|
||||
url_.RawQuery = q
|
||||
} else {
|
||||
url_.RawQuery += "&" + q
|
||||
}
|
||||
return url_.String()
|
||||
}
|
||||
|
||||
func condVal(v string) []string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{v}
|
||||
}
|
||||
|
||||
// Exchange takes a code and gets access Token from the remote server.
|
||||
func (t *Transport) Exchange(code string) (*Token, error) {
|
||||
if t.Config == nil {
|
||||
return nil, OAuthError{"Exchange", "no Config supplied"}
|
||||
}
|
||||
|
||||
// If the transport or the cache already has a token, it is
|
||||
// passed to `updateToken` to preserve existing refresh token.
|
||||
tok := t.Token
|
||||
if tok == nil && t.TokenCache != nil {
|
||||
tok, _ = t.TokenCache.Token()
|
||||
}
|
||||
if tok == nil {
|
||||
tok = new(Token)
|
||||
}
|
||||
err := t.updateToken(tok, url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"redirect_uri": {t.RedirectURL},
|
||||
"scope": {t.Scope},
|
||||
"code": {code},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Token = tok
|
||||
if t.TokenCache != nil {
|
||||
return tok, t.TokenCache.PutToken(tok)
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// RoundTrip executes a single HTTP transaction using the Transport's
|
||||
// Token as authorization headers.
|
||||
//
|
||||
// This method will attempt to renew the Token if it has expired and may return
|
||||
// an error related to that Token renewal before attempting the client request.
|
||||
// If the Token cannot be renewed a non-nil os.Error value will be returned.
|
||||
// If the Token is invalid callers should expect HTTP-level errors,
|
||||
// as indicated by the Response's StatusCode.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
accessToken, err := t.getAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// To set the Authorization header, we must make a copy of the Request
|
||||
// so that we don't modify the Request we were given.
|
||||
// This is required by the specification of http.RoundTripper.
|
||||
req = cloneRequest(req)
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
// Make the HTTP request.
|
||||
return t.transport().RoundTrip(req)
|
||||
}
|
||||
|
||||
func (t *Transport) getAccessToken() (string, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.Token == nil {
|
||||
if t.Config == nil {
|
||||
return "", OAuthError{"RoundTrip", "no Config supplied"}
|
||||
}
|
||||
if t.TokenCache == nil {
|
||||
return "", OAuthError{"RoundTrip", "no Token supplied"}
|
||||
}
|
||||
var err error
|
||||
t.Token, err = t.TokenCache.Token()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the Token if it has expired.
|
||||
if t.Expired() {
|
||||
if err := t.Refresh(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if t.AccessToken == "" {
|
||||
return "", errors.New("no access token obtained from refresh")
|
||||
}
|
||||
return t.AccessToken, nil
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request.
|
||||
// The clone is a shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
// shallow copy of the struct
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
// deep copy of the Header
|
||||
r2.Header = make(http.Header)
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = s
|
||||
}
|
||||
return r2
|
||||
}
|
||||
|
||||
// Refresh renews the Transport's AccessToken using its RefreshToken.
|
||||
func (t *Transport) Refresh() error {
|
||||
if t.Token == nil {
|
||||
return OAuthError{"Refresh", "no existing Token"}
|
||||
}
|
||||
if t.RefreshToken == "" {
|
||||
return OAuthError{"Refresh", "Token expired; no Refresh Token"}
|
||||
}
|
||||
if t.Config == nil {
|
||||
return OAuthError{"Refresh", "no Config supplied"}
|
||||
}
|
||||
|
||||
err := t.updateToken(t.Token, url.Values{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {t.RefreshToken},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.TokenCache != nil {
|
||||
return t.TokenCache.PutToken(t.Token)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthenticateClient gets an access Token using the client_credentials grant
|
||||
// type.
|
||||
func (t *Transport) AuthenticateClient() error {
|
||||
if t.Config == nil {
|
||||
return OAuthError{"Exchange", "no Config supplied"}
|
||||
}
|
||||
if t.Token == nil {
|
||||
t.Token = &Token{}
|
||||
}
|
||||
return t.updateToken(t.Token, url.Values{"grant_type": {"client_credentials"}})
|
||||
}
|
||||
|
||||
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
|
||||
// implements the OAuth2 spec correctly
|
||||
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||
// In summary:
|
||||
// - Reddit only accepts client secret in the Authorization header
|
||||
// - Dropbox accepts either it in URL param or Auth header, but not both.
|
||||
// - Google only accepts URL param (not spec compliant?), not Auth header
|
||||
func providerAuthHeaderWorks(tokenURL string) bool {
|
||||
if strings.HasPrefix(tokenURL, "https://accounts.google.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://github.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://api.instagram.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://www.douban.com/") {
|
||||
// Some sites fail to implement the OAuth2 spec fully.
|
||||
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
|
||||
// to this package to let users select server bugs.
|
||||
return true
|
||||
}
|
||||
|
||||
// updateToken mutates both tok and v.
|
||||
func (t *Transport) updateToken(tok *Token, v url.Values) error {
|
||||
v.Set("client_id", t.ClientId)
|
||||
bustedAuth := !providerAuthHeaderWorks(t.TokenURL)
|
||||
if bustedAuth {
|
||||
v.Set("client_secret", t.ClientSecret)
|
||||
}
|
||||
client := &http.Client{Transport: t.transport()}
|
||||
req, err := http.NewRequest("POST", t.TokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if !bustedAuth {
|
||||
req.SetBasicAuth(t.ClientId, t.ClientSecret)
|
||||
}
|
||||
r, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if r.StatusCode != 200 {
|
||||
return OAuthError{"updateToken", "Unexpected HTTP status " + r.Status}
|
||||
}
|
||||
var b struct {
|
||||
Access string `json:"access_token"`
|
||||
Refresh string `json:"refresh_token"`
|
||||
ExpiresIn int64 `json:"expires_in"` // seconds
|
||||
Id string `json:"id_token"`
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
switch content {
|
||||
case "application/x-www-form-urlencoded", "text/plain":
|
||||
vals, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Access = vals.Get("access_token")
|
||||
b.Refresh = vals.Get("refresh_token")
|
||||
b.ExpiresIn, _ = strconv.ParseInt(vals.Get("expires_in"), 10, 64)
|
||||
b.Id = vals.Get("id_token")
|
||||
default:
|
||||
if err = json.Unmarshal(body, &b); err != nil {
|
||||
return fmt.Errorf("got bad response from server: %q", body)
|
||||
}
|
||||
}
|
||||
if b.Access == "" {
|
||||
return errors.New("received empty access token from authorization server")
|
||||
}
|
||||
tok.AccessToken = b.Access
|
||||
// Don't overwrite `RefreshToken` with an empty value
|
||||
if b.Refresh != "" {
|
||||
tok.RefreshToken = b.Refresh
|
||||
}
|
||||
if b.ExpiresIn == 0 {
|
||||
tok.Expiry = time.Time{}
|
||||
} else {
|
||||
tok.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second)
|
||||
}
|
||||
if b.Id != "" {
|
||||
if tok.Extra == nil {
|
||||
tok.Extra = make(map[string]string)
|
||||
}
|
||||
tok.Extra["id_token"] = b.Id
|
||||
}
|
||||
return nil
|
||||
}
|
||||
236
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go
generated
vendored
Normal file
236
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2011 The goauth2 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 oauth
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var requests = []struct {
|
||||
path, query, auth string // request
|
||||
contenttype, body string // response
|
||||
}{
|
||||
{
|
||||
path: "/token",
|
||||
query: "grant_type=authorization_code&code=c0d3&client_id=cl13nt1d",
|
||||
contenttype: "application/json",
|
||||
auth: "Basic Y2wxM250MWQ6czNjcjN0",
|
||||
body: `
|
||||
{
|
||||
"access_token":"token1",
|
||||
"refresh_token":"refreshtoken1",
|
||||
"id_token":"idtoken1",
|
||||
"expires_in":3600
|
||||
}
|
||||
`,
|
||||
},
|
||||
{path: "/secure", auth: "Bearer token1", body: "first payload"},
|
||||
{
|
||||
path: "/token",
|
||||
query: "grant_type=refresh_token&refresh_token=refreshtoken1&client_id=cl13nt1d",
|
||||
contenttype: "application/json",
|
||||
auth: "Basic Y2wxM250MWQ6czNjcjN0",
|
||||
body: `
|
||||
{
|
||||
"access_token":"token2",
|
||||
"refresh_token":"refreshtoken2",
|
||||
"id_token":"idtoken2",
|
||||
"expires_in":3600
|
||||
}
|
||||
`,
|
||||
},
|
||||
{path: "/secure", auth: "Bearer token2", body: "second payload"},
|
||||
{
|
||||
path: "/token",
|
||||
query: "grant_type=refresh_token&refresh_token=refreshtoken2&client_id=cl13nt1d",
|
||||
contenttype: "application/x-www-form-urlencoded",
|
||||
body: "access_token=token3&refresh_token=refreshtoken3&id_token=idtoken3&expires_in=3600",
|
||||
auth: "Basic Y2wxM250MWQ6czNjcjN0",
|
||||
},
|
||||
{path: "/secure", auth: "Bearer token3", body: "third payload"},
|
||||
{
|
||||
path: "/token",
|
||||
query: "grant_type=client_credentials&client_id=cl13nt1d",
|
||||
contenttype: "application/json",
|
||||
auth: "Basic Y2wxM250MWQ6czNjcjN0",
|
||||
body: `
|
||||
{
|
||||
"access_token":"token4",
|
||||
"expires_in":3600
|
||||
}
|
||||
`,
|
||||
},
|
||||
{path: "/secure", auth: "Bearer token4", body: "fourth payload"},
|
||||
}
|
||||
|
||||
func TestOAuth(t *testing.T) {
|
||||
// Set up test server.
|
||||
n := 0
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
if n >= len(requests) {
|
||||
t.Errorf("too many requests: %d", n)
|
||||
return
|
||||
}
|
||||
req := requests[n]
|
||||
n++
|
||||
|
||||
// Check request.
|
||||
if g, w := r.URL.Path, req.path; g != w {
|
||||
t.Errorf("request[%d] got path %s, want %s", n, g, w)
|
||||
}
|
||||
want, _ := url.ParseQuery(req.query)
|
||||
for k := range want {
|
||||
if g, w := r.FormValue(k), want.Get(k); g != w {
|
||||
t.Errorf("query[%s] = %s, want %s", k, g, w)
|
||||
}
|
||||
}
|
||||
if g, w := r.Header.Get("Authorization"), req.auth; w != "" && g != w {
|
||||
t.Errorf("Authorization: %v, want %v", g, w)
|
||||
}
|
||||
|
||||
// Send response.
|
||||
w.Header().Set("Content-Type", req.contenttype)
|
||||
io.WriteString(w, req.body)
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer server.Close()
|
||||
|
||||
config := &Config{
|
||||
ClientId: "cl13nt1d",
|
||||
ClientSecret: "s3cr3t",
|
||||
Scope: "https://example.net/scope",
|
||||
AuthURL: server.URL + "/auth",
|
||||
TokenURL: server.URL + "/token",
|
||||
}
|
||||
|
||||
// TODO(adg): test AuthCodeURL
|
||||
|
||||
transport := &Transport{Config: config}
|
||||
_, err := transport.Exchange("c0d3")
|
||||
if err != nil {
|
||||
t.Fatalf("Exchange: %v", err)
|
||||
}
|
||||
checkToken(t, transport.Token, "token1", "refreshtoken1", "idtoken1")
|
||||
|
||||
c := transport.Client()
|
||||
resp, err := c.Get(server.URL + "/secure")
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
checkBody(t, resp, "first payload")
|
||||
|
||||
// test automatic refresh
|
||||
transport.Expiry = time.Now().Add(-time.Hour)
|
||||
resp, err = c.Get(server.URL + "/secure")
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
checkBody(t, resp, "second payload")
|
||||
checkToken(t, transport.Token, "token2", "refreshtoken2", "idtoken2")
|
||||
|
||||
// refresh one more time, but get URL-encoded token instead of JSON
|
||||
transport.Expiry = time.Now().Add(-time.Hour)
|
||||
resp, err = c.Get(server.URL + "/secure")
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
checkBody(t, resp, "third payload")
|
||||
checkToken(t, transport.Token, "token3", "refreshtoken3", "idtoken3")
|
||||
|
||||
transport.Token = &Token{}
|
||||
err = transport.AuthenticateClient()
|
||||
if err != nil {
|
||||
t.Fatalf("AuthenticateClient: %v", err)
|
||||
}
|
||||
checkToken(t, transport.Token, "token4", "", "")
|
||||
resp, err = c.Get(server.URL + "/secure")
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
checkBody(t, resp, "fourth payload")
|
||||
}
|
||||
|
||||
func checkToken(t *testing.T, tok *Token, access, refresh, id string) {
|
||||
if g, w := tok.AccessToken, access; g != w {
|
||||
t.Errorf("AccessToken = %q, want %q", g, w)
|
||||
}
|
||||
if g, w := tok.RefreshToken, refresh; g != w {
|
||||
t.Errorf("RefreshToken = %q, want %q", g, w)
|
||||
}
|
||||
if g, w := tok.Extra["id_token"], id; g != w {
|
||||
t.Errorf("Extra['id_token'] = %q, want %q", g, w)
|
||||
}
|
||||
if tok.Expiry.IsZero() {
|
||||
t.Errorf("Expiry is zero; want ~1 hour")
|
||||
} else {
|
||||
exp := tok.Expiry.Sub(time.Now())
|
||||
const slop = 3 * time.Second // time moving during test
|
||||
if (time.Hour-slop) > exp || exp > time.Hour {
|
||||
t.Errorf("Expiry = %v, want ~1 hour", exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkBody(t *testing.T, r *http.Response, body string) {
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Errorf("reading reponse body: %v, want %q", err, body)
|
||||
}
|
||||
if g, w := string(b), body; g != w {
|
||||
t.Errorf("request body mismatch: got %q, want %q", g, w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCachePermissions(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows doesn't support file mode bits.
|
||||
return
|
||||
}
|
||||
|
||||
td, err := ioutil.TempDir("", "oauth-test")
|
||||
if err != nil {
|
||||
t.Fatalf("ioutil.TempDir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
tempFile := filepath.Join(td, "cache-file")
|
||||
|
||||
cf := CacheFile(tempFile)
|
||||
if err := cf.PutToken(new(Token)); err != nil {
|
||||
t.Fatalf("PutToken: %v", err)
|
||||
}
|
||||
fi, err := os.Stat(tempFile)
|
||||
if err != nil {
|
||||
t.Fatalf("os.Stat: %v", err)
|
||||
}
|
||||
if fi.Mode()&0077 != 0 {
|
||||
t.Errorf("Created cache file has mode %#o, want non-accessible to group+other", fi.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenExpired(t *testing.T) {
|
||||
tests := []struct {
|
||||
token Token
|
||||
expired bool
|
||||
}{
|
||||
{Token{AccessToken: "foo"}, false},
|
||||
{Token{AccessToken: ""}, true},
|
||||
{Token{AccessToken: "foo", Expiry: time.Now().Add(-1 * time.Hour)}, true},
|
||||
{Token{AccessToken: "foo", Expiry: time.Now().Add(1 * time.Hour)}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := tt.token.Expired(); got != tt.expired {
|
||||
t.Errorf("token %+v Expired = %v; want %v", tt.token, got, !got)
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/core/http/cgi/testdata/test.cgi
generated
vendored
Normal file
91
Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/core/http/cgi/testdata/test.cgi
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/perl
|
||||
# Copyright 2011 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.
|
||||
#
|
||||
# Test script run as a child process under cgi_test.go
|
||||
|
||||
use strict;
|
||||
use Cwd;
|
||||
|
||||
binmode STDOUT;
|
||||
|
||||
my $q = MiniCGI->new;
|
||||
my $params = $q->Vars;
|
||||
|
||||
if ($params->{"loc"}) {
|
||||
print "Location: $params->{loc}\r\n\r\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
print "Content-Type: text/html\r\n";
|
||||
print "X-CGI-Pid: $$\r\n";
|
||||
print "X-Test-Header: X-Test-Value\r\n";
|
||||
print "\r\n";
|
||||
|
||||
if ($params->{"bigresponse"}) {
|
||||
# 17 MB, for OS X: golang.org/issue/4958
|
||||
for (1..(17 * 1024)) {
|
||||
print "A" x 1024, "\r\n";
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
print "test=Hello CGI\r\n";
|
||||
|
||||
foreach my $k (sort keys %$params) {
|
||||
print "param-$k=$params->{$k}\r\n";
|
||||
}
|
||||
|
||||
foreach my $k (sort keys %ENV) {
|
||||
my $clean_env = $ENV{$k};
|
||||
$clean_env =~ s/[\n\r]//g;
|
||||
print "env-$k=$clean_env\r\n";
|
||||
}
|
||||
|
||||
# NOTE: msys perl returns /c/go/src/... not C:\go\....
|
||||
my $dir = getcwd();
|
||||
if ($^O eq 'MSWin32' || $^O eq 'msys') {
|
||||
if ($dir =~ /^.:/) {
|
||||
$dir =~ s!/!\\!g;
|
||||
} else {
|
||||
my $cmd = $ENV{'COMSPEC'} || 'c:\\windows\\system32\\cmd.exe';
|
||||
$cmd =~ s!\\!/!g;
|
||||
$dir = `$cmd /c cd`;
|
||||
chomp $dir;
|
||||
}
|
||||
}
|
||||
print "cwd=$dir\r\n";
|
||||
|
||||
# A minimal version of CGI.pm, for people without the perl-modules
|
||||
# package installed. (CGI.pm used to be part of the Perl core, but
|
||||
# some distros now bundle perl-base and perl-modules separately...)
|
||||
package MiniCGI;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub Vars {
|
||||
my $self = shift;
|
||||
my $pairs;
|
||||
if ($ENV{CONTENT_LENGTH}) {
|
||||
$pairs = do { local $/; <STDIN> };
|
||||
} else {
|
||||
$pairs = $ENV{QUERY_STRING};
|
||||
}
|
||||
my $vars = {};
|
||||
foreach my $kv (split(/&/, $pairs)) {
|
||||
my ($k, $v) = split(/=/, $kv, 2);
|
||||
$vars->{_urldecode($k)} = _urldecode($v);
|
||||
}
|
||||
return $vars;
|
||||
}
|
||||
|
||||
sub _urldecode {
|
||||
my $v = shift;
|
||||
$v =~ tr/+/ /;
|
||||
$v =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||||
return $v;
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/core/http/testdata/file
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/core/http/testdata/file
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
0123456789
|
||||
1
Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/core/http/testdata/index.html
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/core/http/testdata/index.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
index.html says hello
|
||||
1
Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/core/http/testdata/style.css
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/core/http/testdata/style.css
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
body {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user