agora_dynamic_key 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fc5aad7f3d500a314906d62f0aaf3cfb2266890e
4
+ data.tar.gz: 1d8f2023302be38b0deccd0db0894e60216fe369
5
+ SHA512:
6
+ metadata.gz: e742e625976a71cf5c0ca53b92f3be6ff28435c4170132c4dc12763637f2933aec892053a0263eba5fbb73b070af8b69dee245689941b7ba9871af9b1c28679a
7
+ data.tar.gz: 9a669a7c61cfc91c093fc11e185354dedce2b59f0697f2967862fd2abbc10851ed1d944786849f6b031064b5f44526bb2547c8e853d42cc3120916dad9e34fd9
@@ -0,0 +1,13 @@
1
+ require 'openssl'
2
+ require 'securerandom'
3
+ require 'zlib'
4
+ require 'base64'
5
+
6
+ require_relative 'dynamic_key/sign'
7
+ require_relative 'dynamic_key/access_token'
8
+ require_relative 'dynamic_key/rtc_token_builder'
9
+ require_relative 'dynamic_key/rtm_token_builder'
10
+
11
+ module AgoraDynamicKey
12
+ VERSION = "0.1.0"
13
+ end
@@ -0,0 +1,55 @@
1
+ module AgoraDynamicKey
2
+ module Privilege
3
+ JOIN_CHANNEL = 1
4
+ PUBLISH_AUDIO_STREAM = 2
5
+ PUBLISH_VIDEO_STREAM = 3
6
+ PUBLISH_DATA_STREAM = 4
7
+ RTM_LOGIN = 1000 # for RTM Only
8
+ end
9
+
10
+ class AccessToken
11
+ VERSION = "006"
12
+
13
+ ONE_DAY = 864_00
14
+ SEED = 2 ** 32 - 1
15
+
16
+ attr_accessor :app_id, :channel_name, :app_certificate,
17
+ :uid, :privileges, :privilege_expired_ts,
18
+ :salt, :expired_ts
19
+
20
+ def initialize args={}
21
+ @app_id = args[:app_id]
22
+ @channel_name = args.fetch(:channel_name, "")
23
+ @app_certificate = args[:app_certificate]
24
+ @uid = "#{args.fetch(:uid, "")}"
25
+ @privileges = {}
26
+ @privilege_expired_ts = args[:privilege_expired_ts]
27
+ @salt = SecureRandom.rand(SEED)
28
+ @expired_ts = Time.now.to_i + ONE_DAY
29
+ end
30
+
31
+ def add_privilege privilege, ts
32
+ privileges[privilege] = ts
33
+ end
34
+
35
+ alias grant add_privilege
36
+
37
+ def build
38
+ Sign.encode self
39
+ end
40
+
41
+ def build!
42
+ Sign.encode! self
43
+ end
44
+
45
+ def from_string token
46
+ Sign.decode token
47
+ end
48
+
49
+ def self.generate! payload={}, &block
50
+ token = AccessToken.new payload
51
+ block.call token
52
+ token.build!
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,112 @@
1
+ module AgoraDynamicKey
2
+
3
+ class RTCTokenBuilder
4
+ class InvalidParamsError < StandardError
5
+ attr_reader :params, :missing_keys
6
+ def initialize args={}
7
+ super
8
+ @params = args[:params]
9
+ @missing_keys = args[:missing_keys]
10
+ end
11
+ end
12
+
13
+ module Role
14
+ # DEPRECATED. Role::ATTENDEE has the same privileges as Role::PUBLISHER.
15
+ ATTENDEE = 0
16
+
17
+ # RECOMMENDED. Use this role for a voice/video call or a live broadcast, if your scenario does not require authentication for [Hosting-in](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#hosting-in).
18
+ PUBLISHER = 1
19
+
20
+ # Only use this role if your scenario require authentication for [Hosting-in](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#hosting-in).
21
+ # @note In order for this role to take effect, please contact our support team to enable authentication for Hosting-in for you. Otherwise, Role::SUBSCRIBER still has the same privileges as Role::PUBLISHER.
22
+ SUBSCRIBER = 2
23
+
24
+ # DEPRECATED. Role::ADMIN has the same privileges as Role::PUBLISHER.
25
+ ADMIN = 101
26
+ end
27
+
28
+ class << self
29
+
30
+ #
31
+ # Builds an RTC token using an Integer uid.
32
+ # @param payload
33
+ # :app_id The App ID issued to you by Agora.
34
+ # :app_certificate Certificate of the application that you registered in the Agora Dashboard.
35
+ # :channel_name The unique channel name for the AgoraRTC session in the string format. The string length must be less than 64 bytes. Supported character scopes are:
36
+ # - The 26 lowercase English letters: a to z.
37
+ # - The 26 uppercase English letters: A to Z.
38
+ # - The 10 digits: 0 to 9.
39
+ # - The space.
40
+ # - "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "<", "=", ".", ">", "?", "@", "[", "]", "^", "_", " {", "}", "|", "~", ",".
41
+ # :uid User ID. A 32-bit unsigned integer with a value ranging from 1 to (2^32-1).
42
+ # :role See #userRole.
43
+ # - Role::PUBLISHER; RECOMMENDED. Use this role for a voice/video call or a live broadcast.
44
+ # - Role::SUBSCRIBER: ONLY use this role if your live-broadcast scenario requires authentication for [Hosting-in](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#hosting-in). In order for this role to take effect, please contact our support team to enable authentication for Hosting-in for you. Otherwise, Role_Subscriber still has the same privileges as Role_Publisher.
45
+ # :privilege_expired_ts represented by the number of seconds elapsed since 1/1/1970. If, for example, you want to access the Agora Service within 10 minutes after the token is generated, set expireTimestamp as the current timestamp + 600 (seconds).
46
+ #
47
+ # @return The new Token.
48
+ #
49
+ def build_token_with_uid payload
50
+ check! payload, %i[app_id app_certificate channel_name role uid privilege_expired_ts]
51
+ build_token_with_account @params.merge(:account => @params[:uid])
52
+ end
53
+
54
+ #
55
+ # Builds an RTC token using a string user_account.
56
+ # @param payload
57
+ # :app_id The App ID issued to you by Agora.
58
+ # :app_certificate Certificate of the application that you registered in the Agora Dashboard.
59
+ # :channel_name The unique channel name for the AgoraRTC session in the string format. The string length must be less than 64 bytes. Supported character scopes are:
60
+ # - The 26 lowercase English letters: a to z.
61
+ # - The 26 uppercase English letters: A to Z.
62
+ # - The 10 digits: 0 to 9.
63
+ # - The space.
64
+ # - "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "<", "=", ".", ">", "?", "@", "[", "]", "^", "_", " {", "}", "|", "~", ",".
65
+ # :account The user account.
66
+ # :role See #userRole.
67
+ # - Role::PUBLISHER; RECOMMENDED. Use this role for a voice/video call or a live broadcast.
68
+ # - Role::SUBSCRIBER: ONLY use this role if your live-broadcast scenario requires authentication for [Hosting-in](https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#hosting-in). In order for this role to take effect, please contact our support team to enable authentication for Hosting-in for you. Otherwise, Role_Subscriber still has the same privileges as Role_Publisher.
69
+ # :privilege_expired_ts represented by the number of seconds elapsed since 1/1/1970. If, for example, you want to access the Agora Service within 10 minutes after the token is generated, set expireTimestamp as the current timestamp + 600 (seconds).
70
+ #
71
+ # @return The new Token.
72
+ #
73
+ def build_token_with_account payload
74
+ check! payload, %i[app_id app_certificate channel_name role account privilege_expired_ts]
75
+ @params.merge!(:uid => @params[:account])
76
+ generate_access_token!
77
+ end
78
+
79
+ private
80
+
81
+ # generate access token
82
+ def generate_access_token!
83
+ # Assign appropriate access privileges to each role.
84
+ AgoraDynamicKey::AccessToken.generate!(@params) do |t|
85
+ t.grant AgoraDynamicKey::Privilege::JOIN_CHANNEL, t.privilege_expired_ts
86
+ if @params[:role] == Role::PUBLISHER ||
87
+ @params[:role] == Role::SUBSCRIBER ||
88
+ @params[:role] == Role::ADMIN
89
+ t.grant AgoraDynamicKey::Privilege::PUBLISH_AUDIO_STREAM, t.privilege_expired_ts
90
+ t.grant AgoraDynamicKey::Privilege::PUBLISH_VIDEO_STREAM, t.privilege_expired_ts
91
+ t.grant AgoraDynamicKey::Privilege::PUBLISH_DATA_STREAM, t.privilege_expired_ts
92
+ end
93
+ end
94
+ end
95
+
96
+ # params check
97
+ def check!(payload, args)
98
+ raise InvalidParamsError.new(params: payload), "invalid params" if payload.nil? or payload.empty?
99
+ symbolize_keys payload.select { |key| args.include? key }
100
+ missing_keys = args - @params.keys
101
+ raise InvalidParamsError.new(params: payload, missing_keys: missing_keys), "missing params" if missing_keys.size != 0
102
+ _invalid_params = @params.select { |hash, (k, v)| v.nil? or v.empty?}
103
+ raise InvalidParamsError.new(params: payload), "invalid params" if _invalid_params.empty?
104
+ end
105
+
106
+ # symbolize keys
107
+ def symbolize_keys payload
108
+ @params = payload.inject({}) { |hash, (k, v)| hash[k.to_sym] = v; hash }
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,46 @@
1
+ module AgoraDynamicKey
2
+ class RTMTokenBuilder
3
+ module Role
4
+ RTM_USER = 1
5
+ end
6
+
7
+ class << self
8
+ attr_accessor :token
9
+
10
+ #
11
+ # @param payload
12
+ # :app_id app_id The App ID issued to you by Agora. Apply for a new App ID from
13
+ # Agora Dashboard if it is missing from your kit. See Get an App ID.
14
+ # :app_certificate app_certificate Certificate of the application that you registered in
15
+ # the Agora Dashboard. See Get an App Certificate.
16
+ # :role role AgoraDynamicKey::RTCTokenBuilder::Role::RTM_USER = 1: RTM USER
17
+ # :account User Account.
18
+ # :privilege_expired_ts represented by the number of seconds elapsed since 1/1/1970.
19
+ # If, for example, you want to access the Agora Service within 10 minutes
20
+ # after the token is generated, set expireTimestamp as the current time stamp
21
+ # + 600 (seconds).
22
+ def build_token payload
23
+ check! payload, %i[app_id app_certificate role account privilege_expired_ts]
24
+ token = AccessToken.new @params.merge(:channel_name => @params[:account])
25
+ token.grant Privilege::RTM_LOGIN, @params[:privilege_expired_ts]
26
+ token.build!
27
+ end
28
+
29
+ private
30
+ # params check
31
+ def check!(payload, args)
32
+ raise InvalidParamsError.new(params: payload), "invalid params" if payload.nil? or payload.empty?
33
+ symbolize_keys payload.select { |key| args.include? key }
34
+ missing_keys = args - @params.keys
35
+ raise InvalidParamsError.new(params: payload, missing_keys: missing_keys), "missing params" if missing_keys.size != 0
36
+ _invalid_params = @params.select { |hash, (k, v)| v.nil? or v.empty?}
37
+ raise InvalidParamsError.new(params: payload), "invalid params" if _invalid_params.empty?
38
+ end
39
+
40
+ # symbolize keys
41
+ def symbolize_keys payload
42
+ @params = payload.inject({}) { |hash, (k, v)| hash[k.to_sym] = v; hash }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+
3
+ module AgoraDynamicKey
4
+ class Sign
5
+ MAX_SIZE = 1024
6
+ SHA256 = OpenSSL::Digest.new("sha256")
7
+ class InvalidToken < StandardError; end
8
+
9
+ class << self
10
+ private def parse_map_uint32 map
11
+ binary = [map.size].pack "v"
12
+ binary << map.flatten.pack("vV"*map.size)
13
+ end
14
+
15
+ def encode option
16
+ encode! option
17
+ rescue
18
+ false
19
+ end
20
+
21
+ def encode! option
22
+ # pack message
23
+ message = [option.salt, option.expired_ts].pack "VV" # pack into uint16 with little endian
24
+ message << parse_map_uint32(option.privileges)
25
+
26
+ # generate signature
27
+ to_sign = "#{option.app_id}#{option.channel_name}#{option.uid}#{message}"
28
+
29
+ sign = OpenSSL::HMAC.new(option.app_certificate, SHA256).update(to_sign).digest
30
+
31
+ crc32_channel_name = Zlib::crc32(option.channel_name) & 0xffffffff
32
+ crc32_uid = Zlib::crc32("#{option.uid}") & 0xffffffff
33
+
34
+ uint32_channel_name = [crc32_channel_name].pack "V"
35
+ uint32_uid = [crc32_uid].pack "V"
36
+
37
+ uint16_sign_size = [sign.size].pack "v"
38
+
39
+ uint16_message_size = [message.size].pack "v"
40
+ # generate content
41
+ content = "#{uint16_sign_size}#{sign}#{uint32_channel_name}#{uint32_uid}#{uint16_message_size}#{message}"
42
+ # final content
43
+ "#{AccessToken::VERSION}#{option.app_id}#{Base64.strict_encode64(content)}"
44
+ end
45
+
46
+ def decode string
47
+ docode!
48
+ rescue
49
+ false
50
+ end
51
+
52
+ def decode! string
53
+ version = string[0..2]
54
+ raise InvalidToken, "can't match version" unless version == VERSION
55
+ appId = string[3..3+31]
56
+ content = string[3+32..-1]
57
+ content_binary = Base64.strict_decode64(content)
58
+ uint16_sign_size = content_binary.unpack("v")[0]
59
+ sign = content_binary[2, uint16_sign_size].unpack "H*"
60
+ offset = uint16_sign_size + 2
61
+ uint32_channel_name, uint32_uid = content_binary[offset..offset+1].unpack("VV")
62
+ offset = offset + 8
63
+ uint16_message_size = content_binary[offset..offset+1].unpack("v")[0]
64
+ offset = offset + 2
65
+ message = content_binary[offset..offset+uint16_message_size]
66
+ salt, expired_ts = message[0..7].unpack("V*")
67
+ privileges_size = message[8..9].unpack("v")[0]
68
+ message[10..-1].unpack("vV"*privileges_size)
69
+ true
70
+ end
71
+ end
72
+ end
73
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: agora_dynamic_key
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - matrixbirds
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-08 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A Simple Agora Dynamic Key Implementation
14
+ email: sales@agora.io
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/dynamic_key.rb
20
+ - lib/dynamic_key/access_token.rb
21
+ - lib/dynamic_key/rtc_token_builder.rb
22
+ - lib/dynamic_key/rtm_token_builder.rb
23
+ - lib/dynamic_key/sign.rb
24
+ homepage: https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/ruby/sample
25
+ licenses:
26
+ - MIT
27
+ metadata:
28
+ source_code_uri: https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/ruby
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.5.2.3
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Agora Dynamic Key Client
49
+ test_files: []