atlassian-jwt-0.2.0/0000755000175000017500000000000013564020100013305 5ustar pravipraviatlassian-jwt-0.2.0/bin/0000755000175000017500000000000013564020100014055 5ustar pravipraviatlassian-jwt-0.2.0/bin/console0000755000175000017500000000052213564020100015444 0ustar pravipravi#!/usr/bin/env ruby require "bundler/setup" require "atlassian/jwt" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start atlassian-jwt-0.2.0/bin/setup0000755000175000017500000000020313564020100015136 0ustar pravipravi#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here atlassian-jwt-0.2.0/LICENSE.txt0000644000175000017500000000105613564020100015132 0ustar pravipraviCopyright 2013 Atlassian Pty Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.atlassian-jwt-0.2.0/Rakefile0000644000175000017500000000016513564020100014754 0ustar pravipravirequire "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task :default => :spec atlassian-jwt-0.2.0/Gemfile0000644000175000017500000000014213564020100014575 0ustar pravipravisource 'https://rubygems.org' # Specify your gem's dependencies in atlassian-jwt.gemspec gemspec atlassian-jwt-0.2.0/atlassian-jwt.gemspec0000644000175000017500000000223713564020100017437 0ustar pravipravi# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'atlassian/jwt/version' Gem::Specification.new do |spec| spec.name = 'atlassian-jwt' spec.version = Atlassian::Jwt::VERSION spec.authors = ['Spike Ilacqua', 'Seb Ruiz', 'Ngoc Dao'] spec.email = ['spike@6kites.com', 'seb@sebruiz.net', 'ndao@atlassian.com'] spec.summary = %q{Encode and decode JWT tokens for use with the Atlassian Connect REST APIs.} spec.description = %q{This gem simplifies generating the claims needed to authenticate with the Atlassian Connect REST APIs.} spec.homepage = 'https://bitbucket.org/atlassian/atlassian-jwt-ruby' spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] spec.add_runtime_dependency 'jwt', '~> 2.1.0' spec.add_development_dependency 'json', '~> 2.2.0' spec.add_development_dependency 'rake', '~> 12.3.2' spec.add_development_dependency 'rspec', '~> 3.8.0' end atlassian-jwt-0.2.0/.gitignore0000644000175000017500000000016513564020100015277 0ustar pravipravi/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /.ruby-* /atlassian-jwt-*.gem atlassian-jwt-0.2.0/.rspec0000644000175000017500000000003713564020100014422 0ustar pravipravi--format documentation --color atlassian-jwt-0.2.0/lib/0000755000175000017500000000000013564020100014053 5ustar pravipraviatlassian-jwt-0.2.0/lib/atlassian/0000755000175000017500000000000013564020100016032 5ustar pravipraviatlassian-jwt-0.2.0/lib/atlassian/jwt/0000755000175000017500000000000013564020100016636 5ustar pravipraviatlassian-jwt-0.2.0/lib/atlassian/jwt/version.rb0000644000175000017500000000007613564020100020653 0ustar pravipravimodule Atlassian module Jwt VERSION = "0.2.0" end end atlassian-jwt-0.2.0/lib/atlassian/jwt.rb0000644000175000017500000000460713564020100017172 0ustar pravipravirequire 'atlassian/jwt/version' require 'jwt' require 'uri' require 'cgi' module Atlassian module Jwt class << self CANONICAL_QUERY_SEPARATOR = '&' ESCAPED_CANONICAL_QUERY_SEPARATOR = '%26' def decode(token, secret, validate = true, options = {}) options = {:algorithm => 'HS256'}.merge(options) ::JWT.decode(token, secret, validate, options) end def encode(payload, secret, algorithm = 'HS256', header_fields = {}) ::JWT.encode(payload, secret, algorithm, header_fields) end def create_query_string_hash(uri, http_method, base_uri) Digest::SHA256.hexdigest( create_canonical_request(uri, http_method, base_uri) ) end def create_canonical_request(uri, http_method, base_uri) uri = URI.parse(uri) unless uri.kind_of? URI base_uri = URI.parse(base_uri) unless base_uri.kind_of? URI [ http_method.upcase, canonicalize_uri(uri, base_uri), canonicalize_query_string(uri.query) ].join(CANONICAL_QUERY_SEPARATOR) end def build_claims(issuer, url, http_method, base_url = '', issued_at = nil, expires = nil, attributes = {}) issued_at ||= Time.now.to_i expires ||= issued_at + 60 qsh = Digest::SHA256.hexdigest( Atlassian::Jwt.create_canonical_request(url, http_method, base_url) ) { iss: issuer, iat: issued_at, exp: expires, qsh: qsh }.merge(attributes) end def canonicalize_uri(uri, base_uri) path = uri.path.sub(/^#{base_uri.path}/, '') path = '/' if path.nil? || path.empty? path = '/' + path unless path.start_with? '/' path.chomp!('/') if path.length > 1 path.gsub(CANONICAL_QUERY_SEPARATOR, ESCAPED_CANONICAL_QUERY_SEPARATOR) end def canonicalize_query_string(query) return '' if query.nil? || query.empty? query = CGI::parse(query) query.delete('jwt') query.each do |k, v| query[k] = v.map { |a| CGI.escape a }.join(',') if v.is_a? Array query[k].gsub!('+', '%20') # Use %20, not CGI.escape default of "+" query[k].gsub!('%7E', '~') # Unescape "~" per JS tests end query = Hash[query.sort] query.map { |k,v| "#{CGI.escape k}=#{v}" }.join(CANONICAL_QUERY_SEPARATOR) end end end end atlassian-jwt-0.2.0/bitbucket-pipelines.yml0000644000175000017500000000063713564020100020000 0ustar pravipravi# This is a sample build configuration for Ruby. # Only use spaces to indent your .yml configuration. # ----- # You can specify a custom docker image from Dockerhub as your build environment. image: ruby:2.3.0 pipelines: default: - step: script: # Modify the commands below to build your repository. - ruby --version - bundler --version - bundle install - rakeatlassian-jwt-0.2.0/README.md0000644000175000017500000000765413564020100014600 0ustar pravipravi# Atlassian::Jwt In order to access the [Atlassian Connect REST APIs](https://developer.atlassian.com/cloud/jira/platform/rest/) an app authenticates using a JSON Web Token (JWT). The token is generated using the app's secret key and contains a *claim* which includes the app's key and a hashed version of the API URL the app is accessing. This gem simplifies generating the claim. This gem provides helpers for generating Atlassian specific JWT claims. It also exposes the [ruby-jwt](https://github.com/jwt/ruby-jwt) gem's `encode` and `decode` methods. ## Installation Add this line to your application's Gemfile: ```ruby gem 'atlassian-jwt' ``` And then execute: ```sh bundle ``` Or install it yourself as: ```sh gem install atlassian-jwt ``` ## Generating a JWT Token ```ruby require 'atlassian/jwt' # The URL of the API call, must include the query string, if any url = 'https://jira.atlassian.com/rest/api/latest/issue/JRA-9' # The key of the app as defined in the app description issuer = 'com.atlassian.example' # The HTTP Method (GET, POST, etc) of the API call http_method = 'get' # The shared secret returned when the app is installed shared_secret = '...' claim = Atlassian::Jwt.build_claims(issuer, url, http_method) jwt = JWT.encode(claim, shared_secret) ``` If the base URL of the API is not at the root of the site, i.e. `https://site.atlassian.net/jira/rest/api`, you will need to pass in the base URL to `build_claims`: ```ruby url = 'https://site.atlassian.net/jira/rest/api/latest/issue/JRA-9' base_url = 'https://site.atlassian.net' claim = Atlassian::Jwt.build_claims(issuer, url, http_method, base_url) ``` The generated JWT can then be passed in an `Authorization` header or in the query string: ```ruby # Header uri = URI('https://site.atlassian.net/rest/api/latest/issue/JRA-9') http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Get.new(uri.request_uri) request.initialize_http_header({'Authorization' => "JWT #{jwt}"}) response = http.request(request) ``` ```ruby # Query String uri = URI("https://site.atlassian.net/rest/api/latest/issue/JRA-9?jwt=#{jwt}") http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Get.new(uri.request_uri) response = http.request(request) ``` By default the issue time of the claim is now and the expiration is 60 seconds in the future, these can be overridden: ```ruby claim = Atlassian::Jwt.build_claims( issuer, url, http_method, base_url, (Time.now - 60.seconds).to_i, (Time.now + 1.day).to_i ) ``` ## Decoding a JWT token The JWT from the server is usually returned as a param. The underlying Ruby JWT gem returns an array, with the first element being the claims and the second being the JWT header, which contains information about how the JWT was encoded. ```ruby claims, jwt_header = Atlassian::Jwt.decode(params[:jwt], shared_secret) ``` By default, the JWT gem verifies that the JWT is properly signed with the shared secret and raises an error if it's not. However, sometimes it is necessary to read the JWT first to determine which shared secret is needed. In this case, use `nil` for the shared secret and follow it with `false` to tell the gem to to verify the signature. ```ruby claims, jwt_header = Atlassian::Jwt.decode(params[:jwt], nil, false) ``` See the [ruby-jwt doc](https://github.com/jwt/ruby-jwt) for additional details. ## Development After checking out the repo, run `bin/setup` to install dependencies. Run `rake spec` to run the tests. Run `bin/console` for an interactive prompt that allows you to experiment. Run `bundle exec rake install` to install this gem to your local machine. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`. It will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on Bitbucket at: https://bitbucket.org/atlassian/atlassian-jwt-ruby