cf-uaa-lib 3.14.1 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 46d432989d2af2fe4fa1a5317daffba25c752728
4
- data.tar.gz: 4daf1389ceb41788db00d04324a778b688c7397c
2
+ SHA256:
3
+ metadata.gz: 283326148ca7b9df992d6ea50b3e0161ef74d07626147979c83a5c6e63daec86
4
+ data.tar.gz: b643f26e60fa73ccdee7e9fc7b89b3b3c010e9c9007be12e98e2f7493cc53a6d
5
5
  SHA512:
6
- metadata.gz: 57c54fd8ee9caddf71d4580bd909a52e4bad75a6815cb0a595b05e43536aa2a1774a2eddf5d747f9d2feca37e295d50f0a7966ccaf34b4b6e188e19f58083445
7
- data.tar.gz: 41b36b3d6df7b9b2d5a55d65b0afadc62ac73837823bca76e3cfc4820fd6f3d0c17f57e9731ce1378a3e02fa272b08262712f5deb8895429eb72785911d0c455
6
+ metadata.gz: 110d6d2259dcce0a7e1cac4ffbf24625a32c6a265fc5329adf4d2cc3ade2803aed54012021d8ad2238cd0aeb5259d3b973173671c7ca1b54fe6cc775146a5001
7
+ data.tar.gz: d217c50866d14c2eac62b03dbe406f572398f9f1d747b0a079c26236662474143d6d252b609df03c228123c677b299d1f3b8e700f49af7f0651561d3e715f647
@@ -0,0 +1,11 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "11:00"
8
+ open-pull-requests-limit: 10
9
+ allow:
10
+ - dependency-type: direct
11
+ - dependency-type: indirect
@@ -0,0 +1,29 @@
1
+ name: Ruby Gem
2
+
3
+ on: workflow_dispatch
4
+
5
+ jobs:
6
+ build:
7
+ name: Build + Publish
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ contents: read
11
+ packages: write
12
+
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Set up Ruby 2.6
16
+ uses: actions/setup-ruby@v1
17
+ with:
18
+ ruby-version: 2.6.x
19
+
20
+ - name: Publish to RubyGems
21
+ run: |
22
+ mkdir -p $HOME/.gem
23
+ touch $HOME/.gem/credentials
24
+ chmod 0600 $HOME/.gem/credentials
25
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
26
+ gem build *.gemspec
27
+ gem push *.gem
28
+ env:
29
+ GEM_HOST_API_KEY: "${{ secrets.RUBYGEMS_AUTH_TOKEN }}"
@@ -0,0 +1,27 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ ruby-version: ['2.5', '2.7', '3.0', '3.1']
16
+
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.ruby-version }}
23
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
24
+ - name: Run tests
25
+ run: bundle exec rake
26
+ - name: Run coverage
27
+ run: bundle exec rake cov
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # CloudFoundry UAA Gem
2
- [![Build Status](https://travis-ci.org/cloudfoundry/cf-uaa-lib.png)](https://travis-ci.org/cloudfoundry/cf-uaa-lib)
2
+ ![Build status](https://github.com/cloudfoundry/cf-uaa-lib/actions/workflows/ruby.yml/badge.svg?branch=master)
3
3
  [![Gem Version](https://badge.fury.io/rb/cf-uaa-lib.png)](http://badge.fury.io/rb/cf-uaa-lib)
4
4
 
5
5
  Client gem for interacting with the [CloudFoundry UAA server](https://github.com/cloudfoundry/uaa)
@@ -8,31 +8,133 @@ For documentation see: https://rubygems.org/gems/cf-uaa-lib
8
8
 
9
9
  ## Install from rubygems
10
10
 
11
- $ gem install cf-uaa-lib
11
+ ```plain
12
+ gem install cf-uaa-lib
13
+ ```
12
14
 
13
15
  ## Build from source
14
16
 
15
- $ bundle install
16
- $ gem build cf-uaa-lib.gemspec
17
- $ gem install cf-uaa-lib<version>.gem
17
+ ```plain
18
+ bundle install
19
+ rake install
20
+ ```
18
21
 
19
22
  ## Use the gem
20
23
 
21
- #!/usr/bin/env ruby
22
- require 'uaa'
23
- token_issuer = CF::UAA::TokenIssuer.new("https://uaa.cloudfoundry.com", "vmc")
24
- puts token_issuer.prompts.inspect
25
- token = token_issuer.implicit_grant_with_creds(username: "<your_username>", password: "<your_password>")
26
- token_info = CF::UAA::TokenCoder.decode(token.info["access_token"], nil, nil, false) #token signature not verified
27
- puts token_info["user_name"]
24
+ Create a UAA client that allows users to authenticate with username/password and allow client application to use `openid` scope to invoke `/userinfo` endpoint for the user.
25
+
26
+ ```plain
27
+ uaa create-client decode-token-demo -s decode-token-demo -v \
28
+ --authorized_grant_types password,refresh_token \
29
+ --scope "openid" \
30
+ --authorities uaa.none
31
+ ```
32
+
33
+ Create a user with which to authorize our `decode-token-demo` client application.
34
+
35
+ ```plain
36
+ uaa create-user myuser \
37
+ --email myuser@example.com \
38
+ --givenName "My" \
39
+ --familyName "User" \
40
+ --password myuser_secret
41
+ ```
42
+
43
+ Create this Ruby script (script is available at `examples/password_grant_and_decode_token.rb`):
44
+
45
+ ```ruby
46
+ #!/usr/bin/env ruby
47
+
48
+ require 'uaa'
49
+
50
+ url = ENV["UAA_URL"]
51
+ client, secret = "decode-token-demo", "decode-token-demo"
52
+ username, password = ENV["UAA_USERNAME"], ENV["UAA_PASSWORD"]
53
+
54
+ def show(title, object)
55
+ puts "#{title}: #{object.inspect}"
56
+ puts
57
+ end
58
+
59
+ uaa_options = {}
60
+ uaa_options[:ssl_ca_file] = ENV["UAA_CA_CERT_FILE"] if ENV["UAA_CA_CERT_FILE"]
61
+ show "uaa_options", uaa_options
62
+
63
+ uaa_info = CF::UAA::Info.new(url, uaa_options)
64
+ show "UAA server info", uaa_info.server
65
+
66
+ token_keys = uaa_info.validation_keys_hash
67
+ show "Signing keys for access tokens", token_keys
68
+
69
+ token_issuer = CF::UAA::TokenIssuer.new(url, client, secret, uaa_options)
70
+ show "Login prompts", token_issuer.prompts
71
+
72
+ token = token_issuer.owner_password_grant(username, password, "openid")
73
+ show "User '#{username}' password grant", token
74
+
75
+ auth_header = "bearer #{token.info["access_token"]}"
76
+ show "Auth header for resource server API calls", auth_header
77
+
78
+ userinfo = uaa_info.whoami(auth_header)
79
+ show "User info", userinfo
80
+
81
+ last_exception = nil
82
+ token_keys.each_pair do |keyname, token_key|
83
+ begin
84
+ token_coder = CF::UAA::TokenCoder.new(uaa_options.merge(pkey: token_key["value"], verify: true))
85
+ token_info = token_coder.decode(auth_header)
86
+ show "Decoded access token", token_info
87
+ last_exception = nil
88
+ rescue CF::UAA::Decode => e
89
+ last_exception = e
90
+ end
91
+ end
92
+ raise last_exception if last_exception
93
+ ```
94
+
95
+ To run the script, setup the env vars for your UAA and run the ruby script:
96
+
97
+ ```bash
98
+ export UAA_URL=https://192.168.50.6:8443
99
+ export UAA_CA_CERT_FILE=/path/to/ca.pem
100
+ export UAA_USERNAME=myuser
101
+ export UAA_PASSWORD=myuser_secret
102
+ ruby examples/password_grant_and_decode_token.rb
103
+ ```
104
+
105
+ The output will look similar to:
106
+
107
+ ```plain
108
+ uaa_options: {:ssl_ca_file=>"/var/folders/wd/xnncwqp96rj0v1y2nms64mq80000gn/T/tmp.R6wpXYdC/ca.pem"}
109
+
110
+ UAA server info: {"app"=>{"version"=>"4.19.0"}, "links"=>{"uaa"=>"https://192.168.50.6:8443", "passwd"=>"/forgot_password", "login"=>"https://192.168.50.6:8443", "register"=>"/create_account"}, "zone_name"=>"uaa", "entityID"=>"192.168.50.6:8443", "commit_id"=>"7897100", "idpDefinitions"=>{}, "prompts"=>{"username"=>["text", "Email"], "password"=>["password", "Password"]}, "timestamp"=>"2018-06-13T12:02:09-0700"}
111
+
112
+ Cookie#domain returns dot-less domain name now. Use Cookie#dot_domain if you need "." at the beginning.
113
+ Signing keys for access tokens: {"uaa-jwt-key-1"=>{"kty"=>"RSA", "e"=>"AQAB", "use"=>"sig", "kid"=>"uaa-jwt-key-1", "alg"=>"RS256", "value"=>"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8UNioYHjhyi1qSHrnBZ9\nKE96/1jLBOX2UTShGBo8jP7eDD6zUh5DNHNPAwD1V8gNI4wvNAm+zL1MrSEDWzn2\nPvCANd+XydoNVZU1zhqxvGhoxHmgAA3JbgSS3oLLNDG/HH8wEnjAxb+G1uh2EVSF\nAe/euQ/fEmY4e7uOG34h9WMX84tD1Sf/xvVoNGAL8bTwotzBLFZ12M3P70hrKDi5\n9wEBbY5bllvvNFyjZTYwMbw97RIOdg3FQkOABu8ENCqbPks5gqSpNV33ekaX4rAd\nwYdEX5iUzDBdMyD8jqUopuqTXqBKg2/ealGitXdbSIEAvcBgZWnn1j2vFp6OEYBB\n7wIDAQAB\n-----END PUBLIC KEY-----", "n"=>"APFDYqGB44cotakh65wWfShPev9YywTl9lE0oRgaPIz-3gw-s1IeQzRzTwMA9VfIDSOMLzQJvsy9TK0hA1s59j7wgDXfl8naDVWVNc4asbxoaMR5oAANyW4Ekt6CyzQxvxx_MBJ4wMW_htbodhFUhQHv3rkP3xJmOHu7jht-IfVjF_OLQ9Un_8b1aDRgC_G08KLcwSxWddjNz-9Iayg4ufcBAW2OW5Zb7zRco2U2MDG8Pe0SDnYNxUJDgAbvBDQqmz5LOYKkqTVd93pGl-KwHcGHRF-YlMwwXTMg_I6lKKbqk16gSoNv3mpRorV3W0iBAL3AYGVp59Y9rxaejhGAQe8"}}
114
+
115
+ Login prompts: {"username"=>["text", "Email"], "password"=>["password", "Password"]}
116
+
117
+ User 'myuser' password grant: #<CF::UAA::TokenInfo:0x00007fbad5a12c18 @info={"access_token"=>"eyJhbGciOiJSUzI1NiIsImtpZCI6InVhYS1qd3Qta2V5LTEiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJlMTFlZmMwNjI1OGQ0MzA0YTc4ZGIyNzliYjJjMzQ1OCIsInN1YiI6IjM5NzhmZjRkLWQ3MzgtNGI4Yi05OTA4LTdhZTE0N2YzYzNiZSIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJkZWNvZGUtdG9rZW4tZGVtbyIsImNpZCI6ImRlY29kZS10b2tlbi1kZW1vIiwiYXpwIjoiZGVjb2RlLXRva2VuLWRlbW8iLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiMzk3OGZmNGQtZDczOC00YjhiLTk5MDgtN2FlMTQ3ZjNjM2JlIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoibXl1c2VyIiwiZW1haWwiOiJteXVzZXJAZXhhbXBsZS5jb20iLCJhdXRoX3RpbWUiOjE1MzE2MzAxNDgsInJldl9zaWciOiI5M2E2NzkwNCIsImlhdCI6MTUzMTYzMDE0OCwiZXhwIjoxNTMxNjczMzQ4LCJpc3MiOiJodHRwczovLzE5Mi4xNjguNTAuNjo4NDQzL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbIm9wZW5pZCIsImRlY29kZS10b2tlbi1kZW1vIl19.qtbzxCOW5bebTgMLK-71_zxaT7l5PSmxhXcDtCeA64dZZ6-wXXmJivopm5PFEHnHiZwRpVe43jyEsbJGzBdl8GEsYQ9YIy51-4noby7ClziJv-6rSBYZnZuU5A234QRWclATGksOcz8Ft9PTIKGKLScyLhncwas7W0uiNJ87MFBGWY6Ovvl3Ac5-jHCqiRBXD6vUhzpfmy6_OUr53i9zJgtcQQWgDrOHxnFcRABZcDnhnWdcxh-Hbagtt9dQU46QgpqLJiUvAg-7ypZPGrxnr9UQEO2Q9GrolkbrSeUcfUOkgppxaA_0b6RYpgBR1qg-Ns6jGUxFgPs6Jj8pysfVmA", "token_type"=>"bearer", "refresh_token"=>"6701ddb9397840a1bd339e9f4314448f-r", "expires_in"=>43199, "scope"=>"openid", "jti"=>"e11efc06258d4304a78db279bb2c3458"}>
118
+
119
+ Auth header for resource server API calls: "bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InVhYS1qd3Qta2V5LTEiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJlMTFlZmMwNjI1OGQ0MzA0YTc4ZGIyNzliYjJjMzQ1OCIsInN1YiI6IjM5NzhmZjRkLWQ3MzgtNGI4Yi05OTA4LTdhZTE0N2YzYzNiZSIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJkZWNvZGUtdG9rZW4tZGVtbyIsImNpZCI6ImRlY29kZS10b2tlbi1kZW1vIiwiYXpwIjoiZGVjb2RlLXRva2VuLWRlbW8iLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiMzk3OGZmNGQtZDczOC00YjhiLTk5MDgtN2FlMTQ3ZjNjM2JlIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoibXl1c2VyIiwiZW1haWwiOiJteXVzZXJAZXhhbXBsZS5jb20iLCJhdXRoX3RpbWUiOjE1MzE2MzAxNDgsInJldl9zaWciOiI5M2E2NzkwNCIsImlhdCI6MTUzMTYzMDE0OCwiZXhwIjoxNTMxNjczMzQ4LCJpc3MiOiJodHRwczovLzE5Mi4xNjguNTAuNjo4NDQzL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbIm9wZW5pZCIsImRlY29kZS10b2tlbi1kZW1vIl19.qtbzxCOW5bebTgMLK-71_zxaT7l5PSmxhXcDtCeA64dZZ6-wXXmJivopm5PFEHnHiZwRpVe43jyEsbJGzBdl8GEsYQ9YIy51-4noby7ClziJv-6rSBYZnZuU5A234QRWclATGksOcz8Ft9PTIKGKLScyLhncwas7W0uiNJ87MFBGWY6Ovvl3Ac5-jHCqiRBXD6vUhzpfmy6_OUr53i9zJgtcQQWgDrOHxnFcRABZcDnhnWdcxh-Hbagtt9dQU46QgpqLJiUvAg-7ypZPGrxnr9UQEO2Q9GrolkbrSeUcfUOkgppxaA_0b6RYpgBR1qg-Ns6jGUxFgPs6Jj8pysfVmA"
120
+
121
+ User info: {"user_id"=>"3978ff4d-d738-4b8b-9908-7ae147f3c3be", "user_name"=>"myuser", "name"=>"My User", "given_name"=>"My", "family_name"=>"User", "email"=>"myuser@example.com", "email_verified"=>true, "previous_logon_time"=>nil, "sub"=>"3978ff4d-d738-4b8b-9908-7ae147f3c3be"}
122
+
123
+ Decoded access token: {"jti"=>"e11efc06258d4304a78db279bb2c3458", "sub"=>"3978ff4d-d738-4b8b-9908-7ae147f3c3be", "scope"=>["openid"], "client_id"=>"decode-token-demo", "cid"=>"decode-token-demo", "azp"=>"decode-token-demo", "grant_type"=>"password", "user_id"=>"3978ff4d-d738-4b8b-9908-7ae147f3c3be", "origin"=>"uaa", "user_name"=>"myuser", "email"=>"myuser@example.com", "auth_time"=>1531630148, "rev_sig"=>"93a67904", "iat"=>1531630148, "exp"=>1531673348, "iss"=>"https://192.168.50.6:8443/oauth/token", "zid"=>"uaa", "aud"=>["openid", "decode-token-demo"]}
124
+ >>>>>>> 21ae635... new example script - password grant + decode using token keys
125
+ ```
28
126
 
29
127
  ## Tests
30
128
 
31
129
  Run the tests with rake:
32
130
 
33
- $ bundle exec rake test
131
+ ```plain
132
+ bundle exec rake test
133
+ ```
34
134
 
35
135
  Run the tests and see a fancy coverage report:
36
136
 
37
- $ bundle exec rake cov
137
+ ```plain
138
+ bundle exec rake cov
139
+ ```
38
140
 
data/cf-uaa-lib.gemspec CHANGED
@@ -24,8 +24,6 @@ Gem::Specification.new do |s|
24
24
  s.summary = %q{Client library for CloudFoundry UAA}
25
25
  s.description = %q{Client library for interacting with the CloudFoundry User Account and Authorization (UAA) server. The UAA is an OAuth2 Authorization Server so it can be used by webapps and command line apps to obtain access tokens to act on behalf of users. The tokens can then be used to access protected resources in a Resource Server. This library is for use by UAA client applications or resource servers.}
26
26
 
27
- s.rubyforge_project = "cf-uaa-lib"
28
-
29
27
  s.license = "Apache-2.0"
30
28
  s.files = `git ls-files`.split("\n")
31
29
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -33,15 +31,17 @@ Gem::Specification.new do |s|
33
31
  s.require_paths = ['lib']
34
32
 
35
33
  # dependencies
36
- s.add_dependency 'multi_json', '~> 1.12.0', '>= 1.12.1'
34
+ s.add_dependency 'multi_json', '>= 1.12.1', '< 1.16'
37
35
  s.add_dependency 'httpclient', '~> 2.8', '>= 2.8.2.4'
36
+ s.add_dependency 'addressable', '~> 2.8', '>= 2.8.0'
38
37
 
39
- s.add_development_dependency 'bundler', '~> 1.14'
40
- s.add_development_dependency 'rake', '~> 10.3', '>= 10.3.2'
41
- s.add_development_dependency 'rspec', '~> 2.14', '>= 2.14.1'
42
- s.add_development_dependency 'simplecov', '~> 0.8.2'
38
+ s.add_development_dependency 'bundler', '~> 2.2'
39
+ s.add_development_dependency 'rake', '>= 10.3.2', '~> 13.0'
40
+ s.add_development_dependency 'rspec', '>= 2.14.1', '~> 3.9'
41
+ s.add_development_dependency 'simplecov', '~> 0.21.2'
43
42
  s.add_development_dependency 'simplecov-rcov', '~> 0.2.3'
44
- s.add_development_dependency 'ci_reporter', '~> 1.9', '>= 1.9.2'
45
- s.add_development_dependency 'json_pure', '~> 1.8', '>= 1.8.1'
43
+ s.add_development_dependency 'ci_reporter', '>= 1.9.2', '~> 2.0'
44
+ s.add_development_dependency 'json_pure', '>= 1.8.1', '~> 2.5'
45
+ s.add_development_dependency 'ci_reporter_rspec', '~> 1.0'
46
46
 
47
47
  end
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # uaa create-client decode-token-demo -s decode-token-demo -v \
4
+ # --authorized_grant_types password,refresh_token \
5
+ # --scope "openid" \
6
+ # --authorities uaa.none
7
+
8
+ require 'uaa'
9
+
10
+ url = ENV["UAA_URL"]
11
+ client, secret = "decode-token-demo", "decode-token-demo"
12
+ username, password = ENV["UAA_USERNAME"], ENV["UAA_PASSWORD"]
13
+
14
+ def show(title, object)
15
+ puts "#{title}: #{object.inspect}"
16
+ puts
17
+ end
18
+
19
+ uaa_options = {}
20
+ uaa_options[:ssl_ca_file] = ENV["UAA_CA_CERT_FILE"] if ENV["UAA_CA_CERT_FILE"]
21
+ show "uaa_options", uaa_options
22
+
23
+ uaa_info = CF::UAA::Info.new(url, uaa_options)
24
+ show "UAA server info", uaa_info.server
25
+
26
+ token_keys = uaa_info.validation_keys_hash
27
+ show "Signing keys for access tokens", token_keys
28
+
29
+ token_issuer = CF::UAA::TokenIssuer.new(url, client, secret, uaa_options)
30
+ show "Login prompts", token_issuer.prompts
31
+
32
+ token = token_issuer.owner_password_grant(username, password, "openid")
33
+ show "User '#{username}' password grant", token
34
+
35
+ auth_header = "bearer #{token.info["access_token"]}"
36
+ show "Auth header for resource server API calls", auth_header
37
+
38
+ userinfo = uaa_info.whoami(auth_header)
39
+ show "User info", userinfo
40
+
41
+ last_exception = nil
42
+ token_keys.each_pair do |keyname, token_key|
43
+ begin
44
+ token_coder = CF::UAA::TokenCoder.new(uaa_options.merge(pkey: token_key["value"], verify: true))
45
+ token_info = token_coder.decode(auth_header)
46
+ show "Decoded access token", token_info
47
+ last_exception = nil
48
+ rescue CF::UAA::Decode => e
49
+ last_exception = e
50
+ end
51
+ end
52
+ raise last_exception if last_exception
53
+
data/lib/uaa/http.rb CHANGED
@@ -183,6 +183,8 @@ module Http
183
183
  raise SSLException, "Invalid SSL Cert for #{url}. Use '--skip-ssl-validation' to continue with an insecure target"
184
184
  rescue URI::Error, SocketError, SystemCallError => e
185
185
  raise BadTarget, "error: #{e.message}"
186
+ rescue HTTPClient::ConnectTimeoutError => e
187
+ raise HTTPException.new "http timeout"
186
188
  end
187
189
 
188
190
  def http_request(uri)
data/lib/uaa/info.rb CHANGED
@@ -54,14 +54,6 @@ class Info
54
54
  json_get(target, "/userinfo?schema=openid", key_style, "authorization" => auth_header)
55
55
  end
56
56
 
57
- # Gets various monitoring and status variables from the server.
58
- # Authenticates using +name+ and +pwd+ for basic authentication.
59
- # @param (see Misc.server)
60
- # @return [Hash]
61
- def varz(name, pwd)
62
- json_get(target, "/varz", key_style, "authorization" => Http.basic_auth(name, pwd))
63
- end
64
-
65
57
  # Gets basic information about the target server, including version number,
66
58
  # commit ID, and links to API endpoints.
67
59
  # @return [Hash]
@@ -129,7 +121,7 @@ class Info
129
121
  # @return [Hash] contents of the token
130
122
  def decode_token(client_id, client_secret, token, token_type = "bearer", audience_ids = nil)
131
123
  reply = json_parse_reply(key_style, *request(target, :post, '/check_token',
132
- Util.encode_form(:token => token),
124
+ Util.encode_form(token: token),
133
125
  "authorization" => Http.basic_auth(client_id, client_secret),
134
126
  "content-type" => Http::FORM_UTF8,"accept" => Http::JSON_UTF8))
135
127
 
@@ -146,7 +138,7 @@ class Info
146
138
  # @return [Hash]
147
139
  def password_strength(password)
148
140
  json_parse_reply(key_style, *request(target, :post, '/password/score',
149
- Util.encode_form(:password => password), "content-type" => Http::FORM_UTF8,
141
+ Util.encode_form(password: password), "content-type" => Http::FORM_UTF8,
150
142
  "accept" => Http::JSON_UTF8))
151
143
  end
152
144
 
data/lib/uaa/scim.rb CHANGED
@@ -12,7 +12,7 @@
12
12
  #++
13
13
 
14
14
  require 'uaa/http'
15
- require 'uri'
15
+ require 'addressable/uri'
16
16
 
17
17
  module CF::UAA
18
18
 
@@ -177,7 +177,7 @@ class Scim
177
177
  # @param [String] id the id attribute of the SCIM object
178
178
  # @return [nil]
179
179
  def delete(type, id)
180
- http_delete @target, "#{type_info(type, :path)}/#{URI.encode(id)}", @auth_header, @zone
180
+ http_delete @target, "#{type_info(type, :path)}/#{Addressable::URI.encode(id)}", @auth_header, @zone
181
181
  end
182
182
 
183
183
  # Replaces the contents of a SCIM object.
@@ -192,7 +192,7 @@ class Scim
192
192
  hdrs.merge!('if-match' => etag)
193
193
  end
194
194
  reply = json_parse_reply(@key_style,
195
- *json_put(@target, "#{path}/#{URI.encode(id)}", info, hdrs))
195
+ *json_put(@target, "#{path}/#{Addressable::URI.encode(id)}", info, hdrs))
196
196
 
197
197
  # hide client endpoints that are not quite scim compatible
198
198
  type == :client && !reply ? get(type, info['client_id']): reply
@@ -210,7 +210,7 @@ class Scim
210
210
  hdrs.merge!('if-match' => etag)
211
211
  end
212
212
  reply = json_parse_reply(@key_style,
213
- *json_patch(@target, "#{path}/#{URI.encode(id)}", info, hdrs))
213
+ *json_patch(@target, "#{path}/#{Addressable::URI.encode(id)}", info, hdrs))
214
214
 
215
215
  # hide client endpoints that are not quite scim compatible
216
216
  type == :client && !reply ? get(type, info['client_id']): reply
@@ -258,7 +258,7 @@ class Scim
258
258
  # @param (see #delete)
259
259
  # @return (see #add)
260
260
  def get(type, id)
261
- info = json_get(@target, "#{type_info(type, :path)}/#{URI.encode(id)}",
261
+ info = json_get(@target, "#{type_info(type, :path)}/#{Addressable::URI.encode(id)}",
262
262
  @key_style, headers)
263
263
 
264
264
  fake_client_id(info) if type == :client # hide client reply, not quite scim
@@ -270,7 +270,7 @@ class Scim
270
270
  # @return (client meta)
271
271
  def get_client_meta(client_id)
272
272
  path = type_info(:client, :path)
273
- json_get(@target, "#{path}/#{URI.encode(client_id)}/meta", @key_style, headers)
273
+ json_get(@target, "#{path}/#{Addressable::URI.encode(client_id)}/meta", @key_style, headers)
274
274
  end
275
275
 
276
276
  # Collects all pages of entries from a query
@@ -350,7 +350,7 @@ class Scim
350
350
  req = {"password" => new_password}
351
351
  req["oldPassword"] = old_password if old_password
352
352
  json_parse_reply(@key_style, *json_put(@target,
353
- "#{type_info(:user, :path)}/#{URI.encode(user_id)}/password", req, headers))
353
+ "#{type_info(:user, :path)}/#{Addressable::URI.encode(user_id)}/password", req, headers))
354
354
  end
355
355
 
356
356
  # Change client secret.
@@ -366,18 +366,18 @@ class Scim
366
366
  req = {"secret" => new_secret }
367
367
  req["oldSecret"] = old_secret if old_secret
368
368
  json_parse_reply(@key_style, *json_put(@target,
369
- "#{type_info(:client, :path)}/#{URI.encode(client_id)}/secret", req, headers))
369
+ "#{type_info(:client, :path)}/#{Addressable::URI.encode(client_id)}/secret", req, headers))
370
370
  end
371
371
 
372
372
  def unlock_user(user_id)
373
373
  req = {"locked" => false}
374
374
  json_parse_reply(@key_style, *json_patch(@target,
375
- "#{type_info(:user, :path)}/#{URI.encode(user_id)}/status", req, headers))
375
+ "#{type_info(:user, :path)}/#{Addressable::URI.encode(user_id)}/status", req, headers))
376
376
  end
377
377
 
378
378
  def map_group(group, is_id, external_group, origin = "ldap")
379
379
  key_name = is_id ? :groupId : :displayName
380
- request = {key_name => group, :externalGroup => external_group, :schemas => ["urn:scim:schemas:core:1.0"], :origin => origin }
380
+ request = {key_name => group, externalGroup: external_group, schemas: ["urn:scim:schemas:core:1.0"], origin: origin }
381
381
  result = json_parse_reply(@key_style, *json_post(@target,
382
382
  "#{type_info(:group_mapping, :path)}", request,
383
383
  headers))
@@ -385,7 +385,7 @@ class Scim
385
385
  end
386
386
 
387
387
  def unmap_group(group_id, external_group, origin = "ldap")
388
- http_delete(@target, "#{type_info(:group_mapping, :path)}/groupId/#{group_id}/externalGroup/#{URI.encode(external_group)}/origin/#{origin}",
388
+ http_delete(@target, "#{type_info(:group_mapping, :path)}/groupId/#{group_id}/externalGroup/#{Addressable::URI.encode(external_group)}/origin/#{origin}",
389
389
  @auth_header, @zone)
390
390
  end
391
391
 
@@ -64,8 +64,8 @@ class TokenCoder
64
64
  def self.encode(token_body, options = {}, obsolete1 = nil, obsolete2 = nil)
65
65
  unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
66
66
  # deprecated: def self.encode(token_body, skey, pkey = nil, algo = 'HS256')
67
- warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
68
- options = {:skey => options }
67
+ warn "WARNING: #{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
68
+ options = {skey: options}
69
69
  options[:pkey], options[:algorithm] = obsolete1, obsolete2
70
70
  end
71
71
  options = normalize_options(options)
@@ -95,8 +95,8 @@ class TokenCoder
95
95
  def self.decode(token, options = {}, obsolete1 = nil, obsolete2 = nil)
96
96
  unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
97
97
  # deprecated: def self.decode(token, skey = nil, pkey = nil, verify = true)
98
- warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
99
- options = {:skey => options }
98
+ warn "WARNING: #{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
99
+ options = {skey: options}
100
100
  options[:pkey], options[:verify] = obsolete1, obsolete2
101
101
  end
102
102
  options = normalize_options(options)
@@ -106,10 +106,16 @@ class TokenCoder
106
106
  signing_input = [header_segment, payload_segment].join('.')
107
107
  header = Util.json_decode64(header_segment)
108
108
  payload = Util.json_decode64(payload_segment, (:sym if options[:symbolize_keys]))
109
- return payload unless options[:verify]
109
+ unless options[:verify]
110
+ warn "WARNING: Decoding token without verifying it was signed by its authoring UAA"
111
+ return payload
112
+ end
110
113
  raise SignatureNotAccepted, "Signature algorithm not accepted" unless
111
114
  options[:accept_algorithms].include?(algo = header["alg"])
112
- return payload if algo == 'none'
115
+ if algo == 'none'
116
+ warn "WARNING: Decoding token that explicitly states it has not been signed by an authoring UAA"
117
+ return payload
118
+ end
113
119
  signature = Util.decode64(crypto_segment)
114
120
  if ["HS256", "HS384", "HS512"].include?(algo)
115
121
  raise InvalidSignature, "Signature verification failed" unless
@@ -123,6 +129,17 @@ class TokenCoder
123
129
  payload
124
130
  end
125
131
 
132
+ # Decodes a JWT token to extract its expiry time
133
+ # @param [String] token A JWT token as returned by {TokenCoder.encode}
134
+ # @return [Integer] exp expiry timestamp
135
+ def self.decode_token_expiry(token)
136
+ segments = token.split('.')
137
+ raise InvalidTokenFormat, "Not enough or too many segments" unless [2,3].include? segments.length
138
+ header_segment, payload_segment, crypto_segment = segments
139
+ payload = Util.json_decode64(payload_segment, :sym)
140
+ payload[:exp]
141
+ end
142
+
126
143
  # Takes constant time to compare 2 strings (HMAC digests in this case)
127
144
  # to avoid timing attacks while comparing the HMAC digests
128
145
  # @param [String] a: the first digest to compare
@@ -170,7 +187,7 @@ class TokenCoder
170
187
  unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
171
188
  # deprecated: def initialize(audience_ids, skey, pkey = nil)
172
189
  warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
173
- options = {:audience_ids => options }
190
+ options = {audience_ids: options }
174
191
  options[:skey], options[:pkey] = obsolete1, obsolete2
175
192
  end
176
193
  @options = self.class.normalize_options(options)
@@ -184,7 +201,7 @@ class TokenCoder
184
201
  def encode(token_body = {}, algorithm = nil)
185
202
  token_body[:aud] = @options[:audience_ids] if @options[:audience_ids] && !token_body[:aud] && !token_body['aud']
186
203
  token_body[:exp] = Time.now.to_i + 7 * 24 * 60 * 60 unless token_body[:exp] || token_body['exp']
187
- self.class.encode(token_body, algorithm ? @options.merge(:algorithm => algorithm) : @options)
204
+ self.class.encode(token_body, algorithm ? @options.merge(algorithm: algorithm) : @options)
188
205
  end
189
206
 
190
207
  # Returns hash of values decoded from the token contents. If the
@@ -13,6 +13,7 @@
13
13
 
14
14
  require 'securerandom'
15
15
  require 'uaa/http'
16
+ require 'cgi'
16
17
 
17
18
  module CF::UAA
18
19
 
@@ -72,8 +73,13 @@ class TokenIssuer
72
73
  if scope = Util.arglist(params.delete(:scope))
73
74
  params[:scope] = Util.strlist(scope)
74
75
  end
75
- headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8,
76
- 'authorization' => Http.basic_auth(@client_id, @client_secret) }
76
+ headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8}
77
+ if @basic_auth
78
+ headers['authorization'] = Http.basic_auth(@client_id, @client_secret)
79
+ else
80
+ headers['X-CF-ENCODED-CREDENTIALS'] = 'true'
81
+ headers['authorization'] = Http.basic_auth(CGI.escape(@client_id || ''), CGI.escape(@client_secret || ''))
82
+ end
77
83
  reply = json_parse_reply(@key_style, *request(@token_target, :post,
78
84
  '/oauth/token', Util.encode_form(params), headers))
79
85
  raise BadResponse unless reply[jkey :token_type] && reply[jkey :access_token]
@@ -81,8 +87,8 @@ class TokenIssuer
81
87
  end
82
88
 
83
89
  def authorize_path_args(response_type, redirect_uri, scope, state = random_state, args = {})
84
- params = args.merge(:client_id => @client_id, :response_type => response_type,
85
- :redirect_uri => redirect_uri, :state => state)
90
+ params = args.merge(client_id: @client_id, response_type: response_type,
91
+ redirect_uri: redirect_uri, state: state)
86
92
  params[:scope] = scope = Util.strlist(scope) if scope = Util.arglist(scope)
87
93
  params[:nonce] = state
88
94
  "/oauth/authorize?#{Util.encode_form(params)}"
@@ -109,6 +115,7 @@ class TokenIssuer
109
115
  @target, @client_id, @client_secret = target, client_id, client_secret
110
116
  @token_target = options[:token_target] || target
111
117
  @key_style = options[:symbolize_keys] ? :sym : nil
118
+ @basic_auth = options[:basic_auth] == true ? true : false
112
119
  initialize_http_options(options)
113
120
  end
114
121
 
@@ -137,7 +144,7 @@ class TokenIssuer
137
144
 
138
145
  # the accept header is only here so the uaa will issue error replies in json to aid debugging
139
146
  headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8 }
140
- body = Util.encode_form(credentials.merge(:source => 'credentials'))
147
+ body = Util.encode_form(credentials.merge(source: 'credentials'))
141
148
  status, body, headers = request(@target, :post, uri, body, headers)
142
149
  raise BadResponse, "status #{status}" unless status == 302
143
150
  req_uri, reply_uri = URI.parse(redir_uri), URI.parse(headers['location'])
@@ -194,7 +201,7 @@ class TokenIssuer
194
201
  reply = json_parse_reply(nil, *request(@target, :post, "/autologin", body, headers))
195
202
  raise BadResponse, "no autologin code in reply" unless reply['code']
196
203
  @target + authorize_path_args('code', redirect_uri, scope,
197
- random_state, :code => reply['code'])
204
+ random_state, code: reply['code'])
198
205
  end
199
206
 
200
207
  # Constructs a uri that the client is to return to the browser to direct
@@ -228,8 +235,8 @@ class TokenIssuer
228
235
  rescue URI::InvalidURIError, ArgumentError, BadResponse
229
236
  raise BadResponse, "received invalid response from target #{@target}"
230
237
  end
231
- request_token(:grant_type => 'authorization_code', :code => authcode,
232
- :redirect_uri => ac_params['redirect_uri'])
238
+ request_token(grant_type: 'authorization_code', code: authcode,
239
+ redirect_uri: ac_params['redirect_uri'])
233
240
  end
234
241
 
235
242
  # Uses the instance client credentials in addition to the +username+
@@ -237,15 +244,15 @@ class TokenIssuer
237
244
  # See {http://tools.ietf.org/html/rfc6749#section-4.3}.
238
245
  # @return [TokenInfo]
239
246
  def owner_password_grant(username, password, scope = nil)
240
- request_token(:grant_type => 'password', :username => username,
241
- :password => password, :scope => scope)
247
+ request_token(grant_type: 'password', username: username,
248
+ password: password, scope: scope)
242
249
  end
243
250
 
244
251
  # Uses a one-time passcode obtained from the UAA to get a
245
252
  # token.
246
253
  # @return [TokenInfo]
247
254
  def passcode_grant(passcode, scope = nil)
248
- request_token(:grant_type => 'password', :passcode => passcode, :scope => scope)
255
+ request_token(grant_type: 'password', passcode: passcode, scope: scope)
249
256
  end
250
257
 
251
258
  # Gets an access token with the user credentials used for authentication
@@ -264,14 +271,14 @@ class TokenIssuer
264
271
  # credentials grant. See http://tools.ietf.org/html/rfc6749#section-4.4
265
272
  # @return [TokenInfo]
266
273
  def client_credentials_grant(scope = nil)
267
- request_token(:grant_type => 'client_credentials', :scope => scope)
274
+ request_token(grant_type: 'client_credentials', scope: scope)
268
275
  end
269
276
 
270
277
  # Uses the instance client credentials and the given +refresh_token+ to get
271
278
  # a new access token. See http://tools.ietf.org/html/rfc6749#section-6
272
279
  # @return [TokenInfo] which may include a new refresh token as well as an access token.
273
280
  def refresh_token_grant(refresh_token, scope = nil)
274
- request_token(:grant_type => 'refresh_token', :refresh_token => refresh_token, :scope => scope)
281
+ request_token(grant_type: 'refresh_token', refresh_token: refresh_token, scope: scope)
275
282
  end
276
283
 
277
284
  end