cloudkey 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cloudkey.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'spec/rake/spectask'
5
+
6
+ desc "Run all examples"
7
+ Spec::Rake::SpecTask.new('spec') do |t|
8
+ t.spec_files = FileList['spec/**/*.rb']
9
+ end
data/Readme.md ADDED
@@ -0,0 +1,31 @@
1
+ Cloudkey
2
+ ========
3
+
4
+ Introduction
5
+ ------------
6
+
7
+ This gem, still under development, aims to provide an abstract interface to DailyMotion's Cloud service.
8
+ Currently, it's mostly a rewrite of the Python and Php version.
9
+
10
+ Warning
11
+ -------
12
+
13
+ We were not able to use security features from the web service to craft restricted urls while testing the Python
14
+ or PHP versions, so we supposed they had not been activated yet.
15
+
16
+ Usage
17
+ -----
18
+
19
+ # Fill in your credentials
20
+ @cloudkey = Cloudkey.authenticate USER_ID, KEY
21
+
22
+ # Grab a list of your medias
23
+ p @cloudkey.media.list(:fields => [:id])
24
+
25
+ # Get an embedded player for your video, with its usage restricted to a specific IP address
26
+ p @cloudkey.media.embedded_url VIDEO_ID, Cloudkey::SecurityPolicy.new(:ip => "88.0.0.1")
27
+
28
+ License
29
+ -------
30
+
31
+ Cloudkey is released under the MIT License.
data/cloudkey.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cloudkey/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cloudkey"
7
+ s.version = Cloudkey::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Jean-Hadrien Chabran", "Boubacar Diallo"]
10
+ s.email = ["jh@kareea.com"]
11
+ s.homepage = "http://rubygems.org/gems/cloudkey"
12
+ s.summary = %q{Bla bla}
13
+ s.description = %q{bla bla}
14
+
15
+ s.rubyforge_project = "cloudkey"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency "rspec", "~> 2.0.1"
23
+ s.add_dependency 'curb', "~> 0.7.8"
24
+ end
data/lib/cloudkey.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'curb'
2
+
3
+ require 'cloudkey/version'
4
+ require 'cloudkey/security_level'
5
+ require 'cloudkey/security_policy'
6
+ require 'cloudkey/api'
7
+
8
+ module Cloudkey
9
+ def self.authenticate *args
10
+ API::new *args
11
+ end
12
+ end
@@ -0,0 +1,86 @@
1
+ require 'digest/md5'
2
+
3
+ require 'cloudkey/client'
4
+
5
+ require 'cloudkey/file'
6
+ require 'cloudkey/media'
7
+
8
+ module Cloudkey
9
+ class API
10
+ attr_accessor :user_id, :key, :base_url, :proxy, :act_as_user
11
+ attr_reader :target
12
+
13
+ # New API versions that break compatibility would probably change the endpoint
14
+ END_POINT = "/api"
15
+
16
+ def initialize user_id, key, options={}
17
+ raise "Can't connect without an user_id" unless user_id
18
+ raise "Can't connect without an key" unless key
19
+
20
+ @user_id, @key = user_id, key
21
+
22
+ @base_url = options[:base_url] || 'http://api.dmcloud.net'
23
+ @proxy = options[:proxy]
24
+ @act_as_user = options[:act_as_user]
25
+
26
+ @target = @base_url + END_POINT
27
+ end
28
+
29
+ def user_infos
30
+ if act_as_user
31
+ "#{user_id}/#{act_as_user}"
32
+ else
33
+ user_id
34
+ end
35
+ end
36
+
37
+ def media
38
+ Media.new self
39
+ end
40
+
41
+ def file
42
+ File.new self
43
+ end
44
+
45
+ def self.sign message, secret
46
+ Digest::MD5.hexdigest(message + secret)
47
+ end
48
+
49
+ def self.sign_url url, secret, security_policy = SecurityPolicy.new
50
+ (url, query) = url.split("?")
51
+
52
+ expires = security_policy.expires.to_s
53
+
54
+ rand = (('a'..'z').to_a + (1..9).to_a).shuffle[0..7].join('')
55
+ food = security_policy.level.to_s
56
+ food << url << expires.to_s
57
+ food << rand << secret << security_policy.private_parameters.join('')
58
+ food << security_policy.encoded_public_parameters if security_policy.encoded_public_parameters
59
+
60
+ digest = Digest::MD5.hexdigest(food)
61
+
62
+ result = url
63
+ result << "?"
64
+ result << query + '&' if query
65
+ result << "auth="
66
+ result << expires.to_s << "-"
67
+ result << security_policy.level.to_s << "-"
68
+ result << rand << "-"
69
+ result << digest
70
+ result << "-#{security_policy.encoded_public_parameters}" if security_policy.encoded_public_parameters
71
+
72
+ result
73
+ end
74
+
75
+ def self.normalize payload
76
+ case payload
77
+ when Array
78
+ payload.collect { |element| normalize element }.join('')
79
+ when Hash
80
+ payload.to_a.sort { |a,b| a.first <=> b.first }.collect {|array| array.first.to_s + normalize(array.last)}.join('')
81
+ else
82
+ payload.to_s
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,45 @@
1
+ require 'json'
2
+
3
+ module Cloudkey
4
+ class Client
5
+ def initialize api
6
+ @api = api
7
+ end
8
+
9
+ def method_missing method_id, *args, &block
10
+ call(method_id, *args, &block)
11
+ end
12
+
13
+ protected
14
+ def call method, args={}
15
+ @request = create_request self.class.name.gsub("Cloudkey::",'').downcase, method, args
16
+ authenticate_request @request
17
+
18
+ curl do |c|
19
+ c.http_post @request.to_json
20
+ JSON.parse c.body_str
21
+ end
22
+ end
23
+
24
+ def authenticate_request request
25
+ request[:auth] = "#{@api.user_infos}:#{API::sign(@api.user_infos + API.normalize(request), @api.key)}"
26
+ end
27
+
28
+ def curl url=nil, &block
29
+ c = Curl::Easy.new(url || @api.target) do |c|
30
+ c.useragent = "cloudkey-rb #{Cloudkey::VERSION}"
31
+ c.headers['Content-Type'] = "application/json"
32
+ c.proxy_url = @api.proxy if @api.proxy
33
+ end
34
+
35
+ yield c
36
+ end
37
+
38
+ def create_request name, method, args
39
+ {
40
+ :call => "#{name}.#{method}",
41
+ :args => args
42
+ }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ module Cloudkey
2
+ class File < Client
3
+ def upload_file path, &block
4
+ raise "File not found" unless ::File.exists? path
5
+
6
+ curl(fetch_upload_url) do |c|
7
+ puts "upload! #{c.url}"
8
+ c.multipart_form_post = true
9
+ c.headers = false
10
+ c.follow_location = true
11
+ if block_given?
12
+ c.on_progress do |dl_total, dl_now, ul_total, ul_now|
13
+ block.call(ul_now, ul_total) if ul_total > 0.0
14
+ true
15
+ end
16
+ end
17
+
18
+ c.http_post Curl::PostField.file("file", path)
19
+ JSON.parse c.body_str
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def fetch_upload_url
26
+ upload["result"]["url"]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module Cloudkey
2
+ class Media < Client
3
+ def embedded_url id, security_level = SecurityPolicy.new
4
+ url = "#{@api.base_url}/embed/#{@api.user_id}/#{id}"
5
+ API.sign_url url, @api.key, security_level
6
+ end
7
+
8
+ def stream_url id, asset_name="mp4_h264_aac", security_level=SecurityPolicy.new, cdn_url='http://cdn.dmcloud.net'
9
+ url = "#{cdn_url}/route/#{@api.user_id}/#{id}/#{asset_name}.#{asset_name.split('_')[0]}"
10
+ API.sign_url url, @api.key, security_level
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Cloudkey
2
+ class API
3
+ module SecurityLevel
4
+ NONE = 0
5
+ DELEGATE = 1 << 0
6
+ AS_NUMBER = 1 << 1
7
+ IP = 1 << 2
8
+ USER_AGENT = 1 << 3
9
+ USE_ONCE = 1 << 4
10
+ COUNTRIES = 1 << 5
11
+ REFERERS = 1 << 6
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,100 @@
1
+ require "cgi"
2
+ require "base64"
3
+ require "zlib"
4
+
5
+ module Cloudkey
6
+ class SecurityPolicy
7
+ IP_FORMAT_ERROR = Class.new(Exception)
8
+ REFERERS_FORMAT_ERROR = Class.new(Exception)
9
+
10
+ def initialize opts={}
11
+ @options = {:expires_in => 7200}.merge(opts)
12
+ end
13
+
14
+ def method_missing method_id, *args, &block
15
+ method_id = method_id.to_s
16
+
17
+ affectation = method_id.include? "="
18
+ method_id.gsub!("=",'')
19
+
20
+ if affectation
21
+ @options[method_id.to_sym] = args.first
22
+ end
23
+
24
+ @options[method_id.to_sym]
25
+ end
26
+
27
+ def expires
28
+ @options[:expires_in] + Time.now.tv_sec
29
+ end
30
+
31
+ def none?
32
+ @options.empty? || @options.keys == [:expires_in]
33
+ end
34
+
35
+ def delegate?
36
+ @options[:delegate]
37
+ end
38
+
39
+ def as_number?
40
+ !!@options[:as_number]
41
+ end
42
+
43
+ def ip?
44
+ if @options[:ip]
45
+ raise IP_FORMAT_ERROR unless @options[:ip].match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/)
46
+ true
47
+ end
48
+ end
49
+
50
+ def user_agent?
51
+ @options[:user_agent] && !@options[:user_agent].empty?
52
+ end
53
+
54
+ def use_once?
55
+ @options[:use_once]
56
+ end
57
+
58
+ def countries?
59
+ @options[:countries] && !@options[:countries].empty?
60
+ end
61
+
62
+ def referers?
63
+ if @options[:referers] && !@options[:referers].empty?
64
+ raise REFERERS_FORMAT_ERROR unless @options[:referers].inject(true) { |result, url| result && url.match(/https?:\/\/(.*)/) }
65
+ true
66
+ end
67
+ end
68
+
69
+ def set opts={}
70
+ @options = opts
71
+ end
72
+
73
+ def private_parameters
74
+ parameters = []
75
+ parameters << as_number if as_number?
76
+ parameters << ip if ip?
77
+ parameters << user_agent if user_agent?
78
+ parameters
79
+ end
80
+
81
+ def public_parameters
82
+ parameters = []
83
+ parameters << "cc=#{countries.collect{|c| c.downcase}.join(',')}" if countries?
84
+ parameters << "rf=#{CGI.escape(referers.collect{|r| r.gsub(' ', '%20')}.join(' '))}" if referers?
85
+ parameters unless parameters.empty?
86
+ end
87
+
88
+ def encoded_public_parameters
89
+ Base64.encode64(Zlib::Deflate.deflate(public_parameters.join('&'))).chomp if public_parameters
90
+ end
91
+
92
+ def level
93
+ result = 0
94
+ API::SecurityLevel.constants.each do |constant|
95
+ result |= API::SecurityLevel.const_get(constant) if send("#{constant.to_s.downcase}?")
96
+ end
97
+ result
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,3 @@
1
+ module Cloudkey
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Cloudkey::API do
4
+ describe "Authentication" do
5
+ it "should raise an error without an user_id" do
6
+ lambda {Cloudkey::API.new nil, "foobar"}.should raise_error
7
+ end
8
+
9
+ it "should raise an error without an api_key" do
10
+ lambda {Cloudkey::API.new "bob", nil}.should raise_error
11
+ end
12
+ end
13
+
14
+ describe "Helpers methods" do
15
+ before(:each) do
16
+ @api = Cloudkey::API.new "foo", "bar"
17
+ end
18
+
19
+ it "should return Cloudkey::File when sending :file" do
20
+ @api.file.should be_an_instance_of(Cloudkey::File)
21
+ end
22
+
23
+ it "should return Cloudkey::Media when sending :media" do
24
+ @api.media.should be_an_instance_of(Cloudkey::Media)
25
+ end
26
+ end
27
+
28
+ describe "Normalizing" do
29
+ {
30
+ 'foo42bar' => ['foo', 42, 'bar'],
31
+ 'pink3red2yellow1' => {'yellow' => 1, 'red' => 2, 'pink' => 3},
32
+ 'foo42pink3red2yellow1bar' => ['foo', 42, {'yellow' => 1, 'red' => 2, 'pink' => 3}, 'bar'],
33
+ '12' => [nil, 1,2],
34
+ '' => nil,
35
+ '212345' => {2 => [nil, 1,2], 3 => nil, 4 => 5}
36
+ }.each do |normalized, original|
37
+ it "should normalize #{original.inspect} into #{normalized}" do
38
+ Cloudkey::API.normalize(original).should == normalized
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "Signing" do
44
+ it "should sign 'hello world' with sEcReT_KeY and returns 'b5d93121a6dc87562b46beb8ba809ace'" do
45
+ Cloudkey::API.sign("hello world", "sEcReT_KeY").should == 'b5d93121a6dc87562b46beb8ba809ace'
46
+ end
47
+
48
+ it "it should sign an url" do
49
+ Cloudkey::API.sign_url("http://google.fr","olol", Cloudkey::SecurityPolicy.new(:ip => "192.168.0.1"))
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,53 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ module Cloudkey
4
+ describe SecurityPolicy do
5
+ before(:each) do
6
+ @policy = SecurityPolicy.new
7
+ end
8
+
9
+ describe "Default behavior" do
10
+ it "should default to no policy" do
11
+ @policy.should be_none
12
+ @policy.expires_in.should == 7200
13
+ end
14
+ end
15
+
16
+ describe "IP" do
17
+ it "should accept an IP" do
18
+ @policy.ip = "192.168.0.3"
19
+ (@policy.level & API::SecurityLevel::IP).should be_true
20
+ end
21
+
22
+ it "should accept an IP and a user agent" do
23
+ @policy.set :ip => "192.168.0.1", :user_agent => "Mammouth Browser v0.1"
24
+ (@policy.level & API::SecurityLevel::IP).should be_true
25
+ (@policy.level & API::SecurityLevel::USER_AGENT).should be_true
26
+ end
27
+
28
+ it "should raise an error on bad ip format" do
29
+ @policy.set :ip => "Bob Kelso"
30
+ -> {@policy.level}.should raise_error(SecurityPolicy::IP_FORMAT_ERROR)
31
+ end
32
+ end
33
+
34
+ describe "Referers" do
35
+ it "should accept referers" do
36
+ @policy.referers = %w(http://google.com http://lolcat.com)
37
+ (@policy.level & API::SecurityLevel::REFERERS).should be_true
38
+ end
39
+
40
+ it "should accept only valid refers" do
41
+ @policy.referers = ["http://google.com", ""]
42
+ -> { @policy.level }.should raise_error(SecurityPolicy::REFERERS_FORMAT_ERROR)
43
+ end
44
+ end
45
+
46
+ describe "Expires" do
47
+ it "should accept an expire time" do
48
+ @policy.expires_in = 5000
49
+ @policy.expires_in.should be(5000)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cloudkey do
4
+ context "Public API" do
5
+ it "should provide an authenticate method on Cloudkey" do
6
+ Cloudkey.should respond_to :authenticate
7
+ end
8
+
9
+ it "should count medias" do
10
+ pending
11
+ @cloudkey = Cloudkey.authenticate "foo", "bar"
12
+ @cloudkey.media.count
13
+ end
14
+
15
+ it "should accept an optional proxy" do
16
+ @proxy = "http://my.awesome.proxy.com:3128"
17
+ Cloudkey.authenticate("foo", "bar", :proxy => @proxy).proxy.should == @proxy
18
+ end
19
+
20
+ it "should accept an optional base_url" do
21
+ @base_url = "http://different.api.dmcloud.net"
22
+ Cloudkey.authenticate("foo", "bar", :base_url => @base_url).base_url.should == @base_url
23
+ end
24
+ end
25
+ end
@@ -0,0 +1 @@
1
+ require 'cloudkey'
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudkey
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Jean-Hadrien Chabran
14
+ - Boubacar Diallo
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-11-15 00:00:00 +01:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rspec
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 13
31
+ segments:
32
+ - 2
33
+ - 0
34
+ - 1
35
+ version: 2.0.1
36
+ type: :development
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: curb
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 19
47
+ segments:
48
+ - 0
49
+ - 7
50
+ - 8
51
+ version: 0.7.8
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ description: bla bla
55
+ email:
56
+ - jh@kareea.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files: []
62
+
63
+ files:
64
+ - .gitignore
65
+ - Gemfile
66
+ - Rakefile
67
+ - Readme.md
68
+ - cloudkey.gemspec
69
+ - lib/cloudkey.rb
70
+ - lib/cloudkey/api.rb
71
+ - lib/cloudkey/client.rb
72
+ - lib/cloudkey/file.rb
73
+ - lib/cloudkey/media.rb
74
+ - lib/cloudkey/security_level.rb
75
+ - lib/cloudkey/security_policy.rb
76
+ - lib/cloudkey/version.rb
77
+ - spec/cloudkey/api_spec.rb
78
+ - spec/cloudkey/security_policy_spec.rb
79
+ - spec/cloudkey_spec.rb
80
+ - spec/spec_helper.rb
81
+ has_rdoc: true
82
+ homepage: http://rubygems.org/gems/cloudkey
83
+ licenses: []
84
+
85
+ post_install_message:
86
+ rdoc_options: []
87
+
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ requirements: []
109
+
110
+ rubyforge_project: cloudkey
111
+ rubygems_version: 1.3.7
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Bla bla
115
+ test_files:
116
+ - spec/cloudkey/api_spec.rb
117
+ - spec/cloudkey/security_policy_spec.rb
118
+ - spec/cloudkey_spec.rb
119
+ - spec/spec_helper.rb