innards 0.1.0.pre

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,14 @@
1
+ Copyright (c) 2013 TurboRETS
2
+
3
+ This program is free software: you can redistribute it and/or modify
4
+ it under the terms of the GNU Affero General Public License as
5
+ published by the Free Software Foundation, either version 3 of the
6
+ License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU Affero General Public License for more details.
12
+
13
+ You should have received a copy of the GNU Affero General Public License
14
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,27 @@
1
+ [![Dependency Status](https://gemnasium.com/TurboRETS/innards.png)](https://gemnasium.com/TurboRETS/innards)
2
+ [![Build Status](https://drone.io/github.com/TurboRETS/innards/status.png)](https://drone.io/github.com/TurboRETS/innards/latest)
3
+ [![Code Climate](https://codeclimate.com/github/TurboRETS/innards.png)](https://codeclimate.com/github/TurboRETS/innards)
4
+
5
+ TurboRETS Innards
6
+ =================
7
+
8
+ Gem for accessing RETS Servers
9
+
10
+ TODO
11
+ -
12
+ - [X] RETS Server Login
13
+ - [ ] Fetch Metadata
14
+ - [ ] Fetch Resource/Class Data
15
+ - [ ] GetObject
16
+
17
+ Premature Usage Example
18
+ -
19
+ ```ruby
20
+ require 'innards'
21
+ conn = Innards::Connection.new(:login_url => "http://Joe:Schmoe@www.dis.com:6103/rets/login")
22
+ conn.login!
23
+ ```
24
+
25
+ License
26
+ -
27
+ Distributed under an AGPL license, see LICENSE file
@@ -0,0 +1,12 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ require "rake"
5
+ require "rspec"
6
+ require "rspec/core/rake_task"
7
+
8
+ RSpec::Core::RakeTask.new("spec") do |spec|
9
+ spec.pattern = "spec/**/*_spec.rb"
10
+ end
11
+
12
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ module Innards
2
+ end
3
+
4
+ path = File.expand_path("../innards", __FILE__)
5
+ require "#{path}/version"
6
+ require "#{path}/exceptions"
7
+ require "#{path}/connection"
@@ -0,0 +1,166 @@
1
+ require 'uri'
2
+ require 'excon'
3
+ require 'digest/md5'
4
+
5
+ module Innards
6
+ class Connection
7
+
8
+ include Innards::Exceptions
9
+
10
+ VALID_RETS_VERSIONS = [
11
+ "1.5",
12
+ "1.7",
13
+ "1.7.2",
14
+ "1.8"
15
+ ]
16
+
17
+ VALID_AUTH_MODES = [
18
+ "digest",
19
+ "basic"
20
+ ]
21
+
22
+ DEFAULT_PARAMS = {
23
+ :login_url => "",
24
+ :login_uri => nil,
25
+ :proxy_uri => nil,
26
+ :user_agent => "innards/v#{Innards::Version::STRING}",
27
+ :rets_version => "1.7.2",
28
+ :auth_mode => "digest",
29
+ :cookies => [],
30
+ :digest => {},
31
+ :mock => false
32
+ }
33
+
34
+ def initialize(params = {})
35
+
36
+ @params = DEFAULT_PARAMS.merge(params)
37
+
38
+ raise ArgumentError.new("invalid url [:login_url]") unless has_valid_url?
39
+ raise ArgumentError.new("invalid rets version [:rets_version]") unless has_valid_rets_version?
40
+ raise ArgumentError.new("invalid auth mode [:auth_mode]") unless has_valid_auth_mode?
41
+ raise ArgumentError.new("invalid user agent [:user_agent]") unless has_valid_user_agent?
42
+
43
+ end
44
+
45
+ def login!
46
+ tries ||= 3
47
+ @params[:login_uri] = URI(@params[:login_url])
48
+ @params[:current_request] = @params[:login_uri].path
49
+ make_request_to @params[:current_request]
50
+ true
51
+ rescue AuthenticationRequiredException => e
52
+ retry unless (tries -= 1).zero?
53
+ false
54
+ end
55
+
56
+ protected
57
+ def has_valid_url?
58
+ (@params[:login_url] =~ URI::regexp)
59
+ end
60
+
61
+ def has_valid_rets_version?
62
+ VALID_RETS_VERSIONS.include? @params[:rets_version]
63
+ end
64
+
65
+ def has_valid_auth_mode?
66
+ VALID_AUTH_MODES.include? @params[:auth_mode]
67
+ end
68
+
69
+ def has_valid_user_agent?
70
+ !(@params[:user_agent].empty? || @params[:user_agent].nil?)
71
+ end
72
+
73
+ def connection
74
+ @conn = Excon.new("#{@params[:login_uri].scheme}://#{@params[:login_uri].host}:#{@params[:login_uri].port}") if @conn.nil?
75
+ @conn
76
+ end
77
+
78
+ def make_request_to path
79
+ response = connection.get(:path => path, :headers => build_headers_for(path))
80
+
81
+ parse_cookie_from(response.headers['Set-Cookie']) if response.headers.has_key?('Set-Cookie')
82
+ parse_www_authenticate_from(response.headers['WWW-Authenticate']) if response.headers.has_key?('WWW-Authenticate')
83
+
84
+ raise AuthenticationRequiredException.new "Authentication Required" if response.status == 401
85
+
86
+ end
87
+
88
+ def parse_cookie_from set_cookie_header
89
+ set_cookie_header.scan(/(\w+)=(.+?);/) {
90
+ @params[:cookies].push([$1, $2])
91
+ }
92
+ end
93
+
94
+ def parse_www_authenticate_from www_authenticate_header
95
+ www_authenticate_header.scan(/(\w+)="(.*?)"/) {
96
+ @params[:digest][$1] = $2
97
+ }
98
+ end
99
+
100
+ def build_headers_for path
101
+ headers = {
102
+ 'User-Agent' => @params[:user_agent],
103
+ 'Cookie' => build_cookie_header_for(path),
104
+ }
105
+ headers['Authorization'] = build_www_authenticate_header_for(path) unless @params[:digest].empty?
106
+ headers
107
+ end
108
+
109
+ def build_cookie_header_for path
110
+ URI.encode_www_form(@params[:cookies]) unless @params[:cookies].empty?
111
+ end
112
+
113
+ def build_www_authenticate_header_for path
114
+ cnonce = md5(random)
115
+ header = [
116
+ %Q(Digest username="#{@params[:login_uri].user}"),
117
+ %Q(realm="#{@params[:digest]['realm']}"),
118
+ %Q(nonce="#{@params[:digest]['nonce']}"),
119
+ %Q(uri="#{@params[:current_request]}"),
120
+ %Q(response="#{request_digest(cnonce)}"),
121
+ ]
122
+
123
+ if has_qop?
124
+ fields = [
125
+ %Q(cnonce="#{cnonce}"),
126
+ %Q(qop="#{@params[:digest]['qop']}"),
127
+ %Q(nc=00000001)
128
+ ]
129
+ fields.each { |field| header << field }
130
+ end
131
+
132
+ header << %Q(opaque="#{@params[:digest]['opaque']}") if has_opaque?
133
+ header.join(", ")
134
+ end
135
+
136
+ def request_digest cnonce
137
+ a = [md5(a1), @params[:digest]['nonce'], md5(a2)]
138
+ a.insert(2, "00000001", cnonce, @params[:digest]['qop']) if has_qop?
139
+ md5(a.join(":"))
140
+ end
141
+
142
+ def has_opaque?
143
+ @params[:digest].has_key?('opaque') and !@params[:digest]['opaque'].empty?
144
+ end
145
+
146
+ def has_qop?
147
+ @params[:digest].has_key?('qop') and !@params[:digest]['qop'].empty?
148
+ end
149
+
150
+ def a1
151
+ [@params[:login_uri].user, @params[:digest]['realm'], @params[:login_uri].password].join(":")
152
+ end
153
+
154
+ def a2
155
+ ["GET", @params[:current_request]].join(":")
156
+ end
157
+
158
+ def random
159
+ "%x" % (Time.now.to_i + rand(65535))
160
+ end
161
+
162
+ def md5(str)
163
+ Digest::MD5.hexdigest(str)
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,8 @@
1
+ module Innards
2
+ module Exceptions
3
+
4
+ class AuthenticationRequiredException < Exception
5
+ end
6
+
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ module Innards
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+ BUILD = 'pre'
7
+
8
+ STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: innards
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Paul Trippett
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.6.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.6.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: excon
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.25.1
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.25.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.14.1
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.14.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: guard-rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 3.0.2
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 3.0.2
78
+ description: Innards; the internal parts especially of a structure or mechanism; in
79
+ our case it's the core RETS library for TurboRETS
80
+ email:
81
+ - paul@turborets.com
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - lib/innards/connection.rb
87
+ - lib/innards/exceptions.rb
88
+ - lib/innards/version.rb
89
+ - lib/innards.rb
90
+ - LICENSE.txt
91
+ - README.md
92
+ - Rakefile
93
+ homepage: http://github.com/TurboRETS/innards
94
+ licenses:
95
+ - AGPL
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: 1.3.6
112
+ requirements: []
113
+ rubyforge_project: innards
114
+ rubygems_version: 1.8.24
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: RETS Library for Ruby
118
+ test_files: []