File: jwt.rb

package info (click to toggle)
ruby-json-jwt 1.16.7-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 324 kB
  • sloc: ruby: 2,858; makefile: 4
file content (145 lines) | stat: -rw-r--r-- 4,311 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
require 'openssl'
require 'base64'
require 'faraday'
require 'faraday/follow_redirects'
require 'active_support'
require 'active_support/core_ext'
require 'json/jose'

module JSON
  class JWT < ActiveSupport::HashWithIndifferentAccess
    VERSION = ::File.read(
      ::File.join(::File.dirname(__FILE__), '../../VERSION')
    ).chomp

    attr_accessor :blank_payload
    attr_accessor :signature

    class Exception < StandardError; end
    class InvalidFormat < Exception; end
    class VerificationFailed < Exception; end
    class UnexpectedAlgorithm < VerificationFailed; end

    include JOSE

    def initialize(claims = {})
      @content_type = 'application/jwt'
      self.typ = :JWT
      self.alg = :none
      update claims
      unless claims.nil?
        [:exp, :nbf, :iat].each do |key|
          self[key] = self[key].to_i if self[key]
        end
      end
    end

    def sign(private_key_or_secret, algorithm = :autodetect)
      jws = JWS.new self
      jws.kid ||= private_key_or_secret[:kid] if private_key_or_secret.is_a? JSON::JWK
      jws.alg = algorithm
      jws.sign! private_key_or_secret
    end

    def encrypt(public_key_or_secret, algorithm = :RSA1_5, encryption_method = :'A128CBC-HS256')
      jwe = JWE.new self
      jwe.kid ||= public_key_or_secret[:kid] if public_key_or_secret.is_a? JSON::JWK
      jwe.alg = algorithm
      jwe.enc = encryption_method
      jwe.encrypt! public_key_or_secret
    end

    def to_s
      [
        header.to_json,
        self.to_json,
        signature
      ].collect do |segment|
        Base64.urlsafe_encode64 segment.to_s, padding: false
      end.join('.')
    end

    def as_json(options = {})
      case options[:syntax]
      when :general
        {
          payload: Base64.urlsafe_encode64(self.to_json, padding: false),
          signatures: [{
            protected: Base64.urlsafe_encode64(header.to_json, padding: false),
            signature: Base64.urlsafe_encode64(signature.to_s, padding: false)
          }]
        }
      when :flattened
        {
          protected: Base64.urlsafe_encode64(header.to_json, padding: false),
          payload:   Base64.urlsafe_encode64(self.to_json, padding: false),
          signature: Base64.urlsafe_encode64(signature.to_s, padding: false)
        }
      else
        super
      end
    end

    def to_json *args
      if @blank_payload && args.empty?
        ''
      else
        super
      end
    end

    def update claims
      if claims.nil?
        @blank_payload = true
      else
        super
      end
    end

    def pretty_generate
      [
        JSON.pretty_generate(header),
        JSON.pretty_generate(self)
      ]
    end

    class << self
      def decode_compact_serialized(jwt_string, key_or_secret, algorithms = nil, encryption_methods = nil, allow_blank_payload = false)
        case jwt_string.count('.') + 1
        when JWS::NUM_OF_SEGMENTS
          JWS.decode_compact_serialized jwt_string, key_or_secret, algorithms, allow_blank_payload
        when JWE::NUM_OF_SEGMENTS
          if allow_blank_payload
            raise InvalidFormat.new("JWE w/ blank payload is not supported.")
          else
            JWE.decode_compact_serialized jwt_string, key_or_secret, algorithms, encryption_methods
          end
        else
          raise InvalidFormat.new("Invalid JWT Format. JWT should include #{JWS::NUM_OF_SEGMENTS} or #{JWE::NUM_OF_SEGMENTS} segments.")
        end
      end

      def decode_json_serialized(input, key_or_secret, algorithms = nil, encryption_methods = nil, allow_blank_payload = false)
        input = input.with_indifferent_access
        if (input[:signatures] || input[:signature]).present?
          JWS.decode_json_serialized input, key_or_secret, algorithms, allow_blank_payload
        elsif input[:ciphertext].present?
          JWE.decode_json_serialized input, key_or_secret, algorithms, encryption_methods
        else
          raise InvalidFormat.new("Unexpected JOSE JSON Serialization Format.")
        end
      end

      def pretty_generate(jwt_string)
        decode(jwt_string, :skip_verification).pretty_generate
      end
    end
  end
end

require 'json/jws'
require 'json/jwe'
require 'json/jwk'
require 'json/jwk/jwkizable'
require 'json/jwk/set'
require 'json/jwk/set/fetcher'