agora_dynamic_key 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/dynamic_key.rb +13 -0
- data/lib/dynamic_key/access_token.rb +55 -0
- data/lib/dynamic_key/rtc_token_builder.rb +112 -0
- data/lib/dynamic_key/rtm_token_builder.rb +46 -0
- data/lib/dynamic_key/sign.rb +73 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -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
|
data/lib/dynamic_key.rb
ADDED
@@ -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: []
|