netlify 0.1.0

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.
@@ -0,0 +1,70 @@
1
+ # Multipass implementation used for single-sign-on for resellers
2
+ require "openssl"
3
+ require "base64"
4
+ require "time"
5
+ require "json"
6
+
7
+ module Netlify
8
+ class Multipass
9
+ def initialize(multipass_secret)
10
+ ### Use the Multipass secret to derive two cryptographic keys,
11
+ ### one for encryption, one for signing
12
+ key_material = OpenSSL::Digest.new("sha256").digest(multipass_secret)
13
+ @encryption_key = key_material[ 0,16]
14
+ @signature_key = key_material[16,16]
15
+ end
16
+
17
+ def generate_token(customer_data_hash)
18
+ ### Store the current time in ISO8601 format.
19
+ ### The token will only be valid for a small timeframe around this timestamp.
20
+ customer_data_hash["created_at"] = Time.now.iso8601
21
+
22
+ ### Serialize the customer data to JSON and encrypt it
23
+ ciphertext = encrypt(customer_data_hash.to_json)
24
+
25
+ ### Create a signature (message authentication code) of the ciphertext
26
+ ### and encode everything using URL-safe Base64 (RFC 4648)
27
+ sig = sign(ciphertext)
28
+
29
+ Base64.urlsafe_encode64(ciphertext + sign(ciphertext))
30
+ end
31
+
32
+ def decode_token(token)
33
+ decoded_token = Base64.urlsafe_decode64(token)
34
+ ciphertext, signature = [decoded_token[0..-33], decoded_token[-32..-1]]
35
+
36
+ sig = sign(ciphertext)
37
+
38
+ raise "Bad signature" unless sign(ciphertext) == signature
39
+
40
+ JSON.parse(decrypt(ciphertext))
41
+ end
42
+
43
+ private
44
+ def encrypt(plaintext)
45
+ cipher = OpenSSL::Cipher::Cipher.new("aes-128-cbc")
46
+ cipher.encrypt
47
+ cipher.key = @encryption_key
48
+
49
+ ### Use a random IV
50
+ cipher.iv = iv = cipher.random_iv
51
+
52
+ ### Use IV as first block of ciphertext
53
+ iv + cipher.update(plaintext) + cipher.final
54
+ end
55
+
56
+ def decrypt(ciphertext)
57
+ decipher = OpenSSL::Cipher::Cipher.new("aes-128-cbc")
58
+ decipher.decrypt
59
+ decipher.key = @encryption_key
60
+
61
+ decipher.iv, encrypted = [ciphertext[0..15], ciphertext[16..-1]]
62
+
63
+ decipher.update(encrypted) + decipher.final
64
+ end
65
+
66
+ def sign(data)
67
+ OpenSSL::HMAC.digest("sha256", @signature_key, data)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,69 @@
1
+ require 'digest/sha1'
2
+ require 'uri'
3
+
4
+ module Netlify
5
+ class Site < Model
6
+ fields :id, :state, :premium, :claimed, :name, :custom_domain, :url,
7
+ :admin_url, :deploy_id, :deploy_url, :screenshot_url, :created_at, :updated_at,
8
+ :password, :notification_email, :user_id, :error_message, :required
9
+
10
+ def ready?
11
+ state == "current"
12
+ end
13
+
14
+ def error?
15
+ state == "error"
16
+ end
17
+
18
+ def wait_for_ready(timeout = 900, &block)
19
+ deploy = deploys.get(deploy_id)
20
+ raise "Error fetching deploy #{deploy_id}" unless deploy
21
+ deploy.wait_for_ready(timeout, &block)
22
+ self
23
+ end
24
+
25
+ def update(attributes)
26
+ response = client.request(:put, path, :body => mutable_attributes(attributes))
27
+ process(response.parsed)
28
+ if attributes[:zip] || attributes[:dir]
29
+ deploy = deploys.create(attributes)
30
+ self.deploy_id = deploy.id
31
+ end
32
+ self
33
+ end
34
+
35
+ def destroy!
36
+ client.request(:delete, path)
37
+ true
38
+ end
39
+
40
+ def forms
41
+ Forms.new(client, path)
42
+ end
43
+
44
+ def submissions
45
+ Submissions.new(client, path)
46
+ end
47
+
48
+ def files
49
+ Files.new(client, path)
50
+ end
51
+
52
+ def snippets
53
+ Snippets.new(client, path)
54
+ end
55
+
56
+ def deploys
57
+ Deploys.new(client, path)
58
+ end
59
+
60
+ private
61
+ def mutable_attributes(attributes)
62
+ Hash[*[:name, :custom_domain, :password, :notification_email].map {|key|
63
+ if attributes.has_key?(key) || attributes.has_key?(key.to_s)
64
+ [key, attributes[key] || attributes[key.to_s]]
65
+ end
66
+ }.compact.flatten]
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,18 @@
1
+ require "Netlify/site"
2
+ require "digest/sha1"
3
+
4
+ module Netlify
5
+ class Sites < CollectionProxy
6
+ path "/sites"
7
+
8
+ def create(attributes = {})
9
+ response = client.request(:post, path, :body => Site.new(client, {}).send(:mutable_attributes, attributes))
10
+ Site.new(client, response.parsed).tap do |site|
11
+ if attributes[:zip] || attributes[:dir]
12
+ deploy = site.deploys.create(attributes)
13
+ site.deploy_id = deploy.id
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module Netlify
2
+ class Snippet < Model
3
+ fields :id, :title, :general, :general_position, :goal, :goal_position
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require "Netlify/snippet"
2
+
3
+ module Netlify
4
+ class Snippets < CollectionProxy
5
+ path "/snippets"
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Netlify
2
+ class Submission < Model
3
+ fields :id, :number, :title, :email, :name, :first_name, :last_name,
4
+ :company, :summary, :body, :data, :created_at, :site_url
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ require "Netlify/submission"
2
+
3
+ module Netlify
4
+ class Submissions < CollectionProxy
5
+ path "/submissions"
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module Netlify
2
+ class User < Model
3
+ fields :id, :uid, :email, :affiliate_id, :site_count, :created_at, :last_login
4
+
5
+ def sites
6
+ Sites.new(client, path)
7
+ end
8
+
9
+ def submissions
10
+ Submissions.new(client, path)
11
+ end
12
+
13
+ def forms
14
+ Forms.new(client, path)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ require "Netlify/user"
2
+
3
+ module Netlify
4
+ class Users < CollectionProxy
5
+ path "/users"
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Netlify
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,77 @@
1
+ require 'test_helper'
2
+
3
+ class ClientTest < MiniTest::Unit::TestCase
4
+ attr_reader :client
5
+
6
+ def setup
7
+ @client = Netlify::Client.new(:client_id => "client_id", :client_secret => "client_secret")
8
+ end
9
+
10
+ def test_authorize_url
11
+ expected = URI.parse("https://api.netlify.com/oauth/authorize?response_type=code&client_id=client_id&redirect_uri=http%3A%2F%2Fexample.com%2Fcallback")
12
+ actual = URI.parse(client.authorize_url(:redirect_uri => "http://example.com/callback"))
13
+ assert_equal expected.scheme, actual.scheme
14
+ assert_equal expected.host, actual.host
15
+ assert_equal expected.port, actual.port
16
+ assert_equal expected.query.split("&").sort, actual.query.split("&").sort
17
+ end
18
+
19
+ def test_authorize_from_code
20
+ stub_request(:post, "https://api.netlify.com/oauth/token").to_return(
21
+ :headers => {'Content-Type' => 'application/json'},
22
+ :body => {
23
+ "access_token" => "2YotnFZFEjr1zCsicMWpAA"
24
+ })
25
+ client.authorize_from_code!("authorization_code", :redirect_uri => "http://example.com/callback")
26
+ assert_equal "2YotnFZFEjr1zCsicMWpAA", client.access_token
27
+ end
28
+
29
+ def test_authorize_from_credentials
30
+ stub_request(:post, "https://client_id:client_secret@api.netlify.com/oauth/token").to_return(
31
+ :headers => {'Content-Type' => 'application/json'},
32
+ :body => {
33
+ "access_token" => "2YotnFZFEjr1zCsicMWpAA"
34
+ })
35
+
36
+ client.authorize_from_credentials!
37
+ assert_equal "2YotnFZFEjr1zCsicMWpAA", client.access_token
38
+ end
39
+
40
+ def test_simple_get_request
41
+ stub_request(:get, "https://api.netlify.com/api/v1/sites")
42
+ .with(:headers => {'Authorization' => "Bearer access_token"})
43
+ .to_return(
44
+ :headers => {'Content-Type' => 'application/json'},
45
+ :body => []
46
+ )
47
+
48
+ client.access_token = "access_token"
49
+ response = client.request(:get, "/sites")
50
+ assert_equal [], response.parsed
51
+ end
52
+
53
+ def test_sites
54
+ stub_request(:get, "https://api.netlify.com/api/v1/sites")
55
+ .with(:headers => {'Authorization' => "Bearer access_token"})
56
+ .to_return(
57
+ :headers => {'Content-Type' => 'application/json'},
58
+ :body => JSON.generate([{:url => "http://www.example.com"}])
59
+ )
60
+ client.access_token = "access_token"
61
+ sites = client.sites.all
62
+ assert_equal "http://www.example.com", sites.first.url
63
+ end
64
+
65
+ def test_get_site
66
+ stub_request(:get, "https://api.netlify.com/api/v1/sites/1234")
67
+ .with(:headers => {'Authorization' => "Bearer access_token"})
68
+ .to_return(
69
+ :headers => {'Content-Type' => 'application/json'},
70
+ :body => {:url => "http://www.example.com"}
71
+ )
72
+
73
+ client.access_token = "access_token"
74
+ site = client.sites.get("1234")
75
+ assert_equal "http://www.example.com", site.url
76
+ end
77
+ end
Binary file
@@ -0,0 +1,5 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><title>Test Document</title></head>
4
+ <body><h1>Test</h1></body>
5
+ </html>
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+ require 'digest/sha1'
3
+
4
+ class MultipassTest < MiniTest::Unit::TestCase
5
+ def setup
6
+ @mp = Netlify::Multipass.new("secret")
7
+ end
8
+
9
+ def test_generate_and_decode_token
10
+ data = {"email" => "test@example.com", "uid" => "1234"}
11
+ token = @mp.generate_token(data)
12
+ assert_equal data, @mp.decode_token(token), "Data should be the same after generating and decoding"
13
+ end
14
+ end
@@ -0,0 +1,66 @@
1
+ require 'test_helper'
2
+ require 'digest/sha1'
3
+
4
+ class SitesTest < MiniTest::Unit::TestCase
5
+ attr_reader :client
6
+ attr_accessor :_assertions
7
+
8
+ def setup
9
+ @client = Netlify::Client.new(:client_id => "client_id", :client_secret => "client_secret")
10
+ @client.access_token = "access_token"
11
+ self._assertions = 0
12
+ end
13
+
14
+ def test_create_from_dir
15
+ body = nil
16
+ dir = ::File.expand_path("../files/site-dir", __FILE__)
17
+ index_sha = Digest::SHA1.hexdigest(::File.read(::File.join(dir, "index.html")))
18
+
19
+ stub_request(:post, "https://api.netlify.com/api/v1/sites")
20
+ .to_return {|request|
21
+ {
22
+ :headers => {'Content-Type' => 'application/json'},
23
+ :body => JSON.generate({:id => "1234"})
24
+ }
25
+ }
26
+ stub_request(:post, "https://api.netlify.com/api/v1/sites/1234/deploys")
27
+ .to_return {|request|
28
+ body = JSON.parse(request.body)
29
+ {
30
+ :headers => {'Content-Type' => 'application/json'},
31
+ :body => JSON.generate({:id => "2345", :state => "uploading", :required => [index_sha]})
32
+ }
33
+ }
34
+ stub_request(:put, "https://api.netlify.com/api/v1/deploys/2345/files/index.html")
35
+ stub_request(:get, "https://api.netlify.com/api/v1/deploys/2345")
36
+ .to_return(:headers => {'Content-Type' => 'application/json'}, :body => {:id => "2345", :state => "processing"})
37
+
38
+ site = client.sites.create(:dir => dir)
39
+
40
+ assert_equal index_sha, body['files']['/index.html']
41
+
42
+ assert_requested :put, "https://api.netlify.com/api/v1/deploys/2345/files/index.html",
43
+ :body => ::File.read(::File.join(dir, "index.html")), :times => 1 # ===> Success
44
+ end
45
+
46
+ def test_create_from_zip
47
+ stub_request(:post, "https://api.netlify.com/api/v1/sites")
48
+ .to_return({
49
+ :headers => {'Content-Type' => 'application/json'},
50
+ :body => JSON.generate({:id => "1234"})
51
+ })
52
+
53
+ stub_request(:post, "https://api.netlify.com/api/v1/sites/1234/deploys")
54
+ .to_return {|request|
55
+ {
56
+ :headers => {'Content-Type' => 'application/json'},
57
+ :body => JSON.generate({:id => "2345", :state => "uploading", :required => []})
58
+ }
59
+ }
60
+ zip = ::File.expand_path("../files/site-dir.zip", __FILE__)
61
+ site = client.sites.create(:zip => zip)
62
+
63
+ assert_requested :post, "https://api.netlify.com/api/v1/sites/1234/deploys",
64
+ :body => ::File.read(zip), :times => 1
65
+ end
66
+ end
@@ -0,0 +1,6 @@
1
+ require 'Netlify'
2
+ require 'json'
3
+ require 'minitest'
4
+ require 'minitest/autorun'
5
+ # require 'minitest/pride'
6
+ require 'webmock/minitest'
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: netlify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mathias Biilmann Christensen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oauth2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: slop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: highline
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: API Client for Netlify
84
+ email:
85
+ - mathias@Netlify.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - Gemfile
91
+ - Gemfile.lock
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - lib/netlify.rb
96
+ - lib/netlify/access_token.rb
97
+ - lib/netlify/access_tokens.rb
98
+ - lib/netlify/client.rb
99
+ - lib/netlify/collection_proxy.rb
100
+ - lib/netlify/deploy.rb
101
+ - lib/netlify/deploys.rb
102
+ - lib/netlify/dns_record.rb
103
+ - lib/netlify/dns_records.rb
104
+ - lib/netlify/dns_zone.rb
105
+ - lib/netlify/dns_zones.rb
106
+ - lib/netlify/file.rb
107
+ - lib/netlify/files.rb
108
+ - lib/netlify/form.rb
109
+ - lib/netlify/forms.rb
110
+ - lib/netlify/model.rb
111
+ - lib/netlify/multipass.rb
112
+ - lib/netlify/site.rb
113
+ - lib/netlify/sites.rb
114
+ - lib/netlify/snippet.rb
115
+ - lib/netlify/snippets.rb
116
+ - lib/netlify/submission.rb
117
+ - lib/netlify/submissions.rb
118
+ - lib/netlify/user.rb
119
+ - lib/netlify/users.rb
120
+ - lib/netlify/version.rb
121
+ - test/client_test.rb
122
+ - test/files/site-dir.zip
123
+ - test/files/site-dir/index.html
124
+ - test/multipass_test.rb
125
+ - test/sites_test.rb
126
+ - test/test_helper.rb
127
+ homepage: https://www.netlify.com
128
+ licenses: []
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubyforge_project:
146
+ rubygems_version: 2.2.2
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: API Client for Netlify
150
+ test_files:
151
+ - test/client_test.rb
152
+ - test/files/site-dir.zip
153
+ - test/files/site-dir/index.html
154
+ - test/multipass_test.rb
155
+ - test/sites_test.rb
156
+ - test/test_helper.rb