atlassian-jwt 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/atlassian-jwt.gemspec +27 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bitbucket-pipelines.yml +14 -0
- data/lib/atlassian/jwt.rb +76 -0
- data/lib/atlassian/jwt/version.rb +5 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 95822a295a203005eef8ab806c8271e186e79e60
|
4
|
+
data.tar.gz: d86ec07e14e5ac7b4e1328b94026e38f84520e95
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5ca63e222c259304cbfd59a9bdc93c7748afbfe7f99ccff349044a64ce28625e5c2fbb95329db43803118613e211d350e8e508942f3bc4daf4ed9ee5a9d67826
|
7
|
+
data.tar.gz: a7596769f75d9f03d904c47fbb75ef5db2080b5ead8a395b26d5289f2be4e2e574a2b4cccba6b69d9352aa245eb5c42a33db2c72fc640d9b06bb9868477454ae
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# Atlassian::Jwt
|
2
|
+
|
3
|
+
This gem provides helpers for generating Atlassian specific JWT
|
4
|
+
claims. It also exposes the [ruby-jwt](https://github.com/jwt/ruby-jwt)
|
5
|
+
gem's `encode` and `decode` methods.
|
6
|
+
|
7
|
+
[](https://bitbucket.org/atlassian/atlassian-jwt-ruby/addon/pipelines/home)
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'atlassian-jwt'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install atlassian-jwt
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
In order to access the
|
28
|
+
[Atlassian Connect REST APIs](https://developer.atlassian.com/static/connect/docs/latest/rest-apis/)
|
29
|
+
an add-on authenticates using a JSON Web Token (JWT). The token is
|
30
|
+
generated using the add-on's secret key and contains a *claim* which
|
31
|
+
includes the add-on's key and a hashed version of the API URL the
|
32
|
+
add-on is accessing. This gem simplifies generating the claim.
|
33
|
+
|
34
|
+
### Generating a JWT Token
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'atlassian/jwt'
|
38
|
+
|
39
|
+
# The URL of the API call, must include the query string, if any
|
40
|
+
url = 'https://jira.atlassian.com/rest/api/latest/issue/JRA-9'
|
41
|
+
# The key of the add-on as defined in the add-on description
|
42
|
+
issuer = 'com.atlassian.example'
|
43
|
+
http_method = 'get' # The HTTP Method (GET, POST, etc) of the API call
|
44
|
+
shared_secret = '...' # "sharedSecret", returned when the add-on is installed.
|
45
|
+
|
46
|
+
claim = Atlassian::Jwt.build_claims(issuer,url,http_method)
|
47
|
+
jwt = JWT.encode(claim,shared_secret)
|
48
|
+
```
|
49
|
+
|
50
|
+
If the base URL of the API is not at the root of the site,
|
51
|
+
i.e. *https://site.atlassian.net/jira/rest/api*, you will need to pass
|
52
|
+
in the base URL to `#.build_claims`:
|
53
|
+
|
54
|
+
```
|
55
|
+
url = 'https://site.atlassian.net/jira/rest/api/latest/issue/JRA-9'
|
56
|
+
base_url = 'https://site.atlassian.net'
|
57
|
+
|
58
|
+
claim = Atlassian::Jwt.build_claims(issuer, url, http_method, base_url)
|
59
|
+
```
|
60
|
+
|
61
|
+
The generated JWT can then be passed in an 'Authentication' header or
|
62
|
+
in the query string:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
# Header
|
66
|
+
uri = URI('https://site.atlassian.net/rest/api/latest/issue/JRA-9')
|
67
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
68
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
69
|
+
request.initialize_http_header({'Authentication' => "JWT #{jwt}"})
|
70
|
+
response = http.request(request)
|
71
|
+
```
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# Query String
|
75
|
+
uri = URI("https://site.atlassian.net/rest/api/latest/issue/JRA-9?jwt=#{jwt}")
|
76
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
77
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
78
|
+
response = http.request(request)
|
79
|
+
```
|
80
|
+
|
81
|
+
By default the issue time of the claim is now and the expiration is 60
|
82
|
+
seconds in the future, these can be overridden:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
claim = Atlassian::Jwt.build_claims(
|
86
|
+
issuer,
|
87
|
+
url,
|
88
|
+
http_method,
|
89
|
+
base_url,
|
90
|
+
Time.now - 60.seconds
|
91
|
+
Time.now + 1.day
|
92
|
+
)
|
93
|
+
```
|
94
|
+
|
95
|
+
### Decoding a JWT token
|
96
|
+
|
97
|
+
The JWT from the server is usually returned a param. The underlying
|
98
|
+
Ruby JWT gem returns an array with the first element being the claim
|
99
|
+
and the second being the JWT header, which contains information about
|
100
|
+
how the JWT was encoded.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
claims, jwt_header = Atlassian::Jwt.decode(params[:jwt], shared_secret)
|
104
|
+
```
|
105
|
+
|
106
|
+
By default, the JWT gem verifies that the JWT is properly signed with
|
107
|
+
the shared secret and raises an error if it's not. However, sometimes
|
108
|
+
is necessary to read the JWT first to determine which shared secret is
|
109
|
+
needed. In this case, use nil for the shared secret and follow it with
|
110
|
+
`false` to tell the gem to to verify the signature.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
claims, jwt_header = Atlassian::Jwt.decode(params[:jwt], nil, false)
|
114
|
+
```
|
115
|
+
|
116
|
+
See the [ruby-jwt doc](https://github.com/jwt/ruby-jwt) for additional
|
117
|
+
details.
|
118
|
+
|
119
|
+
## Development
|
120
|
+
|
121
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
122
|
+
run `rake spec` to run the tests. You can also run `bin/console` for an
|
123
|
+
interactive prompt that will allow you to experiment.
|
124
|
+
|
125
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
126
|
+
To release a new version, update the version number in `version.rb`, and
|
127
|
+
then run `bundle exec rake release`, which will create a git tag for the
|
128
|
+
version, push git commits and tags, and push the `.gem` file to
|
129
|
+
[rubygems.org](https://rubygems.org).
|
130
|
+
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
Bug reports and pull requests are welcome on Bitbucket at
|
134
|
+
https://bitbucket.org/atlassian/atlassian-jwt-ruby.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'atlassian/jwt/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "atlassian-jwt"
|
8
|
+
spec.version = Atlassian::Jwt::VERSION
|
9
|
+
spec.authors = ["Spike Ilacqua", "Seb Ruiz"]
|
10
|
+
spec.email = ["spike@6kites.com", "sruiz@atlassian.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Encode and decode JWT tokens for use with the Atlassian Connect REST APIs.}
|
13
|
+
spec.description = %q{This gem simplifies generating the claims need to authenticate with the Atlassian Connect REST APIs.}
|
14
|
+
spec.homepage = "https://bitbucket.org/atlassian/atlassian-jwt-ruby"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'jwt', '~> 1.5'
|
22
|
+
spec.add_development_dependency 'json', '~> 1.8'
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "atlassian/jwt"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# This is a sample build configuration for Ruby.
|
2
|
+
# Only use spaces to indent your .yml configuration.
|
3
|
+
# -----
|
4
|
+
# You can specify a custom docker image from Dockerhub as your build environment.
|
5
|
+
image: ruby:2.3.0
|
6
|
+
|
7
|
+
pipelines:
|
8
|
+
default:
|
9
|
+
- step:
|
10
|
+
script: # Modify the commands below to build your repository.
|
11
|
+
- ruby --version
|
12
|
+
- bundler --version
|
13
|
+
- bundle install
|
14
|
+
- rake
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'atlassian/jwt/version'
|
2
|
+
require 'jwt'
|
3
|
+
require 'uri'
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
module Atlassian
|
7
|
+
module Jwt
|
8
|
+
class << self
|
9
|
+
CANONICAL_QUERY_SEPARATOR = '&'
|
10
|
+
ESCAPED_CANONICAL_QUERY_SEPARATOR = '%26'
|
11
|
+
|
12
|
+
def decode(token,secret, validate = true, options = {})
|
13
|
+
options = { :algorithm => 'HS256' }.merge(options)
|
14
|
+
::JWT.decode token, secret, validate, options
|
15
|
+
end
|
16
|
+
|
17
|
+
def encode(payload, secret, algorithm = 'HS256', header_fields = {})
|
18
|
+
::JWT.encode payload, secret, algorithm, header_fields
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_query_string_hash(uri, http_method, base_uri)
|
22
|
+
Digest::SHA256.hexdigest(
|
23
|
+
create_canonical_request(uri, http_method, base_uri)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_canonical_request(uri, http_method, base_uri)
|
28
|
+
uri = URI.parse(uri) unless uri.kind_of? URI
|
29
|
+
base_uri = URI.parse(base_uri) unless base_uri.kind_of? URI
|
30
|
+
|
31
|
+
path = canonicalize_uri(uri, base_uri)
|
32
|
+
|
33
|
+
[http_method.upcase,
|
34
|
+
canonicalize_uri(uri, base_uri),
|
35
|
+
canonicalize_query_string(uri.query)
|
36
|
+
].join(CANONICAL_QUERY_SEPARATOR)
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_claims(issuer,url,http_method,base_url='',issued_at=nil,expires=nil,attributes={})
|
40
|
+
issued_at ||= Time.now.to_i
|
41
|
+
expires ||= issued_at + 60
|
42
|
+
qsh = Digest::SHA256.hexdigest(
|
43
|
+
Atlassian::Jwt.create_canonical_request(url,http_method,base_url)
|
44
|
+
)
|
45
|
+
|
46
|
+
{
|
47
|
+
iss: issuer,
|
48
|
+
iat: issued_at,
|
49
|
+
exp: expires,
|
50
|
+
qsh: qsh
|
51
|
+
}.merge(attributes)
|
52
|
+
end
|
53
|
+
|
54
|
+
def canonicalize_uri(uri, base_uri)
|
55
|
+
path = uri.path.sub(/^#{base_uri.path}/,'')
|
56
|
+
path = '/' if path.nil? || path.empty?
|
57
|
+
path = '/' + path unless path.start_with? '/'
|
58
|
+
path.chomp!('/') if path.length > 1
|
59
|
+
path.gsub(CANONICAL_QUERY_SEPARATOR, ESCAPED_CANONICAL_QUERY_SEPARATOR)
|
60
|
+
end
|
61
|
+
|
62
|
+
def canonicalize_query_string(query)
|
63
|
+
return '' if query.nil? || query.empty?
|
64
|
+
query = CGI::parse(query)
|
65
|
+
query.delete('jwt')
|
66
|
+
query.each do |k, v|
|
67
|
+
query[k] = v.map {|a| CGI.escape a }.join(',') if v.is_a? Array
|
68
|
+
query[k].gsub!('+','%20') # Use %20, not CGI.escape default of "+"
|
69
|
+
query[k].gsub!('%7E','~') # Unescape "~" per JS tests
|
70
|
+
end
|
71
|
+
query = Hash[query.sort]
|
72
|
+
query.map {|k,v| "#{CGI.escape k}=#{v}" }.join('&')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: atlassian-jwt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Spike Ilacqua
|
8
|
+
- Seb Ruiz
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-08-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: jwt
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ~>
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.5'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.5'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: json
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.8'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.8'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.11'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.11'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rake
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '10.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '10.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rspec
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '3.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ~>
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '3.0'
|
84
|
+
description: This gem simplifies generating the claims need to authenticate with the
|
85
|
+
Atlassian Connect REST APIs.
|
86
|
+
email:
|
87
|
+
- spike@6kites.com
|
88
|
+
- sruiz@atlassian.com
|
89
|
+
executables: []
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- .gitignore
|
94
|
+
- .rspec
|
95
|
+
- Gemfile
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- atlassian-jwt.gemspec
|
99
|
+
- bin/console
|
100
|
+
- bin/setup
|
101
|
+
- bitbucket-pipelines.yml
|
102
|
+
- lib/atlassian/jwt.rb
|
103
|
+
- lib/atlassian/jwt/version.rb
|
104
|
+
homepage: https://bitbucket.org/atlassian/atlassian-jwt-ruby
|
105
|
+
licenses: []
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.0.14.1
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: Encode and decode JWT tokens for use with the Atlassian Connect REST APIs.
|
127
|
+
test_files: []
|