proof-sharepoint-ruby 1.0.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,96 @@
1
+ require 'sharepoint-stringutils'
2
+
3
+ module Sharepoint
4
+ class ObjectProperties
5
+ attr_accessor :site, :data, :updated_data
6
+
7
+ def initialize site, data
8
+ @site = site
9
+ @data = data
10
+ @updated_data = Hash.new
11
+ @properties = Hash.new
12
+ @properties_names = Array.new
13
+ @properties_original_names = Array.new
14
+ initialize_properties
15
+ end
16
+
17
+ def add_property property, value = nil
18
+ editable = is_property_editable? property
19
+ property = property.to_s
20
+ @data[property] = nil if @data[property].nil?
21
+ @data[property] = value unless value.nil?
22
+ @updated_data[property] = value if (@initialize_properties == false) and (editable == true)
23
+ unless @properties_original_names.include? property
24
+ @properties_names << property.underscore.to_sym
25
+ @properties_original_names << property
26
+ define_singleton_method property.underscore do
27
+ get_property property
28
+ end
29
+ define_singleton_method property.underscore + '=' do |new_value|
30
+ @data[property] = new_value
31
+ @updated_data[property] = new_value
32
+ end if editable == true
33
+ end
34
+ end
35
+
36
+ def add_properties properties
37
+ properties.each do |property|
38
+ add_property property
39
+ end
40
+ end
41
+
42
+ def available_properties
43
+ @properties_names
44
+ end
45
+
46
+ private
47
+
48
+ def initialize_properties
49
+ @initialize_properties = true
50
+ # Create the properties defined for the Sharepoint type used
51
+ self.class.fields.each do |field|
52
+ add_property field[:name], field[:default] if field[:access].include? :read
53
+ end
54
+ # Set the values and create any missing properties from the OData object
55
+ @data.dup.each do |key,value|
56
+ add_property key, value
57
+ end
58
+ @initialize_properties = false
59
+ end
60
+
61
+ def get_property property_name
62
+ data = @data[property_name]
63
+ if not @properties[property_name].nil?
64
+ @properties[property_name]
65
+ elsif data.class == Hash
66
+ if not data['__deferred'].nil?
67
+ @properties[property_name] = get_deferred_property property_name
68
+ elsif not data['__metadata'].nil?
69
+ @properties[property_name] = @site.class.make_object_from_data @site, data
70
+ else
71
+ @properties[property_name] = data
72
+ end
73
+ elsif not data.nil?
74
+ @properties[property_name] = data
75
+ else
76
+ @properties[property_name] = nil
77
+ end
78
+ end
79
+
80
+ def get_deferred_property property_name
81
+ deferred_data = @data[property_name]['__deferred']
82
+ uri = deferred_data['uri'].gsub /^http.*\/_api\/web\//i, ''
83
+ @site.query :get, uri
84
+ end
85
+
86
+ def is_property_editable? property_name
87
+ # We don't know a priori what the fields are for a generic object, so leave the validation work to the user
88
+ return true if self.is_a?(GenericSharepointObject)
89
+
90
+ self.class.fields.each do |field|
91
+ return field[:access].include? :write if field[:name] == property_name
92
+ end
93
+ false
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,152 @@
1
+ require 'curb'
2
+ require 'json'
3
+ require 'sharepoint-error'
4
+ require 'sharepoint-session'
5
+ require 'sharepoint-object'
6
+ require 'sharepoint-types'
7
+
8
+ module Sharepoint
9
+ class SPException < SharepointError
10
+ def initialize data, uri = nil, body = nil
11
+ @data = data['error']
12
+ @uri = uri
13
+ @body = body
14
+ end
15
+
16
+ def lang ; @data['message']['lang'] ; end
17
+ def message ; @data['message']['value'] ; end
18
+ def code ; @data['code'] ; end
19
+ def uri ; @uri ; end
20
+ def request_body ; @body ; end
21
+
22
+ def inspect
23
+ "#<Sharepoint::SPException: #{{'error'=>@data}}>"
24
+ end
25
+ end
26
+
27
+ class Site
28
+ attr_reader :server_url
29
+ attr_accessor :url, :protocol
30
+ attr_accessor :session
31
+ attr_accessor :name
32
+ attr_accessor :verbose
33
+
34
+ class << self
35
+ def make_object_from_response instance, data
36
+ if data['d']['results'].nil?
37
+ data['d'] = data['d'][data['d'].keys.first] if data['d']['__metadata'].nil?
38
+ if not data['d'].nil?
39
+ make_object_from_data instance, data['d']
40
+ else
41
+ nil
42
+ end
43
+ else
44
+ array = Array.new
45
+ data['d']['results'].each do |result|
46
+ array << (make_object_from_data instance, result)
47
+ end
48
+ array
49
+ end
50
+ end
51
+
52
+ # Uses sharepoint's __metadata field to solve which Ruby class to instantiate,
53
+ # and return the corresponding Sharepoint::Object.
54
+ def make_object_from_data instance, data
55
+ return data unless data.is_a? Hash
56
+
57
+ type_name = data['__metadata']['type'].gsub(/^SP\./, '')
58
+ .gsub(/^Collection\(Edm\.String\)/, 'CollectionString')
59
+ .gsub(/^Collection\(Edm\.Int32\)/, 'CollectionInteger')
60
+ type_parts = type_name.split '.'
61
+ type_name = type_parts.pop
62
+ constant = Sharepoint
63
+ type_parts.each do |part| constant = constant.const_get part end
64
+
65
+ klass = constant.const_get type_name rescue nil
66
+ if klass
67
+ klass.new instance, data
68
+ # Patch for Sharepoint 2013 on-prem, missing period between list name
69
+ # and object type.
70
+ elsif data['__metadata']['type'] =~ /SP\.Data\..+Item/
71
+ Sharepoint::ListItem.new instance, data
72
+ else
73
+ Sharepoint::GenericSharepointObject.new type_name, instance, data
74
+ end
75
+ end
76
+ end
77
+
78
+ def initialize server_url, site_name
79
+ @server_url = server_url
80
+ @name = site_name
81
+ @url = "#{@server_url}/#{@name}"
82
+ @session = Session.new self
83
+ @web_context = nil
84
+ @protocol = 'https'
85
+ @verbose = false
86
+ end
87
+
88
+ def authentication_path
89
+ "#{@protocol}://#{@server_url}/_forms/default.aspx?wa=wsignin1.0"
90
+ end
91
+
92
+ def api_path uri
93
+ "#{@protocol}://#{@url}/_api/web/#{uri}"
94
+ end
95
+
96
+ def filter_path uri
97
+ uri
98
+ end
99
+
100
+ def context_info
101
+ query :get, ''
102
+ end
103
+
104
+ # Sharepoint uses 'X-RequestDigest' as a CSRF security-like.
105
+ # The form_digest method acquires a token or uses a previously acquired
106
+ # token if it is still supposed to be valid.
107
+ def form_digest
108
+ if @web_context.nil? or (not @web_context.is_up_to_date?)
109
+ @getting_form_digest = true
110
+ @web_context = query :post, "#{@protocol}://#{@url}/_api/contextinfo"
111
+ @getting_form_digest = false
112
+ end
113
+ @web_context.form_digest_value
114
+ end
115
+
116
+ def query method, uri, body = nil, skip_json=false, &block
117
+ uri = if uri =~ /^http/ then uri else api_path(uri) end
118
+ arguments = [ uri ]
119
+ arguments << body if method != :get
120
+ result = Curl::Easy.send "http_#{method}", *arguments do |curl|
121
+ curl.headers["Cookie"] = @session.cookie
122
+ curl.headers["Accept"] = "application/json;odata=verbose"
123
+ if method != :get
124
+ curl.headers["Content-Type"] = curl.headers["Accept"]
125
+ if session.instance_of?(Sharepoint::HttpAuth::Session)
126
+ curl.headers["X-RequestDigest"] = form_digest unless @getting_form_digest == true
127
+ else
128
+ curl.headers["X-RequestDigest"] = form_digest unless @getting_form_digest == true
129
+ curl.headers["Authorization"] = "Bearer " + form_digest unless @getting_form_digest == true
130
+ end
131
+ end
132
+ curl.verbose = @verbose
133
+ @session.send :curl, curl unless not @session.methods.include? :curl
134
+ block.call curl unless block.nil?
135
+ end
136
+ if !(skip_json || (result.body_str.nil? || result.body_str.empty?))
137
+ begin
138
+ data = JSON.parse result.body_str
139
+ raise Sharepoint::SPException.new data, uri, body unless data['error'].nil?
140
+ self.class.make_object_from_response self, data
141
+ rescue JSON::ParserError => e
142
+ raise SharepointError.new("Exception with body=#{body}, e=#{e.inspect}, #{e.backtrace.inspect}, response=#{result.body_str}")
143
+ end
144
+ elsif result.status.to_i >= 400
145
+ raise SharepointError.new("#{method.to_s.upcase} #{uri} responded with #{result.status}")
146
+ else
147
+ result.body_str
148
+ end
149
+ end
150
+ end
151
+ end
152
+
@@ -0,0 +1,92 @@
1
+ require 'erb'
2
+ require 'curb'
3
+
4
+ module Sharepoint
5
+ MICROSOFT_STS_URL = "https://login.microsoftonline.com/extSTS.srf"
6
+
7
+ module Soap
8
+ class Authenticate
9
+ SOURCE = "soap/authenticate.xml.erb"
10
+
11
+ attr_accessor :username, :password, :login_url
12
+
13
+ def self.initialize
14
+ return if @initialized == true
15
+ @erb = ERB.new(::File.read ::File.dirname(__FILE__) + '/' + SOURCE)
16
+ @erb.filename = SOURCE
17
+ @erb.def_method self, 'render()'
18
+ @initialized = true
19
+ end
20
+
21
+ def initialize params = {}
22
+ Authenticate.initialize
23
+ @username = params[:username]
24
+ @password = params[:password]
25
+ @login_url = params[:url]
26
+ end
27
+ end
28
+ end
29
+
30
+ class Session
31
+ class ConnexionToStsFailed < Exception ; end
32
+ class ConnexionToSharepointFailed < Exception; end
33
+ class UnknownAuthenticationError < Exception; end
34
+ class AuthenticationFailed < Exception
35
+ def initialize message ; super message ; end
36
+ end
37
+
38
+ attr_accessor :site
39
+
40
+ def initialize site
41
+ @site = site
42
+ end
43
+
44
+ def authenticate user, password, sts_url = nil
45
+ sts_url ||= MICROSOFT_STS_URL
46
+ authenticate_to_sts user, password, sts_url
47
+ get_access_token
48
+ end
49
+
50
+ def cookie
51
+ "FedAuth=#{@fed_auth};rtFa=#{@rtFa}"
52
+ end
53
+
54
+ private
55
+ def authenticate_to_sts user, password, sts_url
56
+ query = Soap::Authenticate.new username: user, password: password, url: @site.authentication_path
57
+ response = Curl::Easy.http_post sts_url, query.render rescue raise ConnexionToStsFailed.new
58
+
59
+ response.body_str.scan(/<wsse:BinarySecurityToken[^>]*>([^<]+)</) do
60
+ offset = ($~.offset 1)
61
+ @security_token = response.body[offset[0]..offset[1] - 1]
62
+ end
63
+ authentication_failed response.body_str if @security_token.nil?
64
+ end
65
+
66
+ def get_cookie_from_header header, cookie_name
67
+ result = nil
68
+ header.scan(/#{cookie_name}=([^;]+);/) do
69
+ offset = $~.offset 1
70
+ result = header[offset[0]..offset[1] - 1]
71
+ end
72
+ result
73
+ end
74
+
75
+ def get_access_token
76
+ http = Curl::Easy.http_post @site.authentication_path, @security_token
77
+ @rtFa = get_cookie_from_header http.header_str, 'rtFa'
78
+ @fed_auth = get_cookie_from_header http.header_str, 'FedAuth'
79
+ raise UnknownAuthenticationError.new if @fed_auth.nil? or @rtFa.nil?
80
+ end
81
+
82
+ def authentication_failed xml
83
+ message = 'Unknown authentication error'
84
+ xml.scan(/<psf:text[^>]*>([^<]+)</) do
85
+ offset = ($~.offset 1)
86
+ message = xml[offset[0]..offset[1] - 1]
87
+ end
88
+ raise AuthenticationFailed.new message
89
+ end
90
+ end
91
+ end
92
+
@@ -0,0 +1,37 @@
1
+ # Defines underscore and pluralize methods
2
+ # unless they've already been defined by another script.
3
+
4
+ unless String.new.methods.include? :underscore
5
+ class String
6
+ def underscore
7
+ self.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
10
+ tr("-", "_").
11
+ downcase
12
+ end
13
+ end
14
+ end
15
+
16
+ unless String.new.methods.include? :camelize
17
+ class String
18
+ def camelize
19
+ self.split("_").each {|s| s.capitalize! }.join("")
20
+ end
21
+ end
22
+ end
23
+
24
+ unless String.new.methods.include? :pluralize
25
+ class String
26
+ def pluralize
27
+ if self.match /y$/
28
+ self.gsub /y$/, 'ies'
29
+ elsif self.match /us$/
30
+ self.gsub /us$/, 'i'
31
+ else
32
+ self + 's'
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,84 @@
1
+ require 'sharepoint-error'
2
+ require 'sharepoint-object'
3
+
4
+ module Sharepoint
5
+ class UnsupportedType < SharepointError
6
+ attr_accessor :type_name
7
+
8
+ def initialize type_name
9
+ @type_name = type_name
10
+ end
11
+
12
+ def message
13
+ "unsupported type '#{@type_name}'"
14
+ end
15
+ end
16
+
17
+ module Type
18
+ def initialize site, data = nil
19
+ data ||= Hash.new
20
+ data['__metadata'] ||= {
21
+ 'type' => "SP.#{self.class.name.split('::').last}"
22
+ }
23
+ super site, data
24
+ end
25
+ end
26
+ end
27
+
28
+ require 'sharepoint-users'
29
+ require 'sharepoint-lists'
30
+ require 'sharepoint-files'
31
+ require 'sharepoint-fields'
32
+ require 'date'
33
+
34
+ module Sharepoint
35
+ ##
36
+ ## Sharepoint Management
37
+ ##
38
+ class Web < Sharepoint::Object
39
+ include Sharepoint::Type
40
+
41
+ field 'CustomMasterUrl'
42
+ field 'Description'
43
+ field 'EnableMinimalDownload'
44
+ field 'MasterUrl'
45
+ field 'QuickLaunchEnabled'
46
+ field 'SaveSiteAsTemplateEnabled'
47
+ field 'SyndicationEnabled'
48
+ field 'Title'
49
+ field 'TreeViewEnabled'
50
+ field 'UiVersion'
51
+ field 'UiVersionConfigurationEnabled'
52
+
53
+ method :apply_theme
54
+ method :break_role_inheritance, default_params: ({ clearsubscopes: true })
55
+ end
56
+
57
+ class ContextWebInformation < Sharepoint::Object
58
+ include Sharepoint::Type
59
+
60
+ def issued_time
61
+ strtime = (form_digest_value.split ',').last
62
+ DateTime.strptime strtime, '%d %b %Y %H:%M:%S %z'
63
+ end
64
+
65
+ def timeout_time
66
+ issued_time + Rational(form_digest_timeout_seconds, 86400)
67
+ end
68
+
69
+ def is_up_to_date?
70
+ DateTime.now < timeout_time
71
+ end
72
+ end
73
+
74
+ class FeatureCollection < Sharepoint::Object
75
+ include Sharepoint::Type
76
+ end
77
+
78
+ ##
79
+ ## Other types
80
+ ##
81
+ class PropertyValues < Sharepoint::Object
82
+ include Sharepoint::Type
83
+ end
84
+ end