sharepoint-ruby 0.0.2

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ae2fa825b6ae80d590550ff62b460e1dadcf48eb
4
+ data.tar.gz: 929e567c7977f3da76dfb0111578b422f195f5a1
5
+ SHA512:
6
+ metadata.gz: 738befa9bedaf7032b6d3e7f00bc01fcd3145d16d4e44c35f18f38b86ab15267405b14ac1bd7f19573a481a66ec96ca21b1b62266c81ea47355422af6a5da343
7
+ data.tar.gz: f869e68e7d377126d05651a06022d23c02660cfd132d192306472f65e6fa21c63050e1993250018daa8a75f5a180e5c302820778b51cc8c61ff5ca9b94ced807
@@ -0,0 +1,126 @@
1
+ module Sharepoint
2
+ class Field < Sharepoint::Object
3
+ include Sharepoint::Type
4
+ sharepoint_resource
5
+ belongs_to :list
6
+
7
+ field 'DefaultValue'
8
+ field 'Description'
9
+ field 'Direction'
10
+ field 'EnforceUniqueValues'
11
+ field 'FieldTypeKind'
12
+ field 'Group'
13
+ field 'Hidden'
14
+ field 'Indexed'
15
+ field 'JSLink'
16
+ field 'ReadOnlyField'
17
+ field 'Required'
18
+ field 'SchemaXml'
19
+ field 'Scope'
20
+ field 'Sealed'
21
+ field 'Sortable'
22
+ field 'StaticName'
23
+ field 'Title'
24
+ field 'TypeAsString'
25
+ #field 'ValidationFormula'
26
+ #field 'ValidationMessage'
27
+ end
28
+
29
+ module Taxonomy
30
+ class TaxonomyField < Field
31
+ end
32
+ end
33
+
34
+ class FieldCalculated < Field
35
+ field 'DateFormat'
36
+ field 'Formula'
37
+ field 'OutputType'
38
+ end
39
+
40
+ class FieldComputed < Field
41
+ field 'EnableLookup'
42
+ end
43
+
44
+ class FieldDateTime < Field
45
+ field 'DateTimeCalendarType'
46
+ field 'DisplayFormat'
47
+ field 'FriendlyDisplayFormat'
48
+ end
49
+
50
+ class FieldGeolocation < Field
51
+ end
52
+
53
+ class FieldGuid < Field
54
+ end
55
+
56
+ class FieldLookup < Field
57
+ field 'AllowMultipleValues'
58
+ field 'IsRelationship'
59
+ field 'LookUpField'
60
+ field 'LookUpList'
61
+ field 'LookUpWebId'
62
+ field 'PrimaryFieldId'
63
+ field 'RelationshipDeleteBehavior'
64
+
65
+ def create_uri
66
+ unless parent.nil?
67
+ parent.__metadata['uri'] + '/fields/addfield'
68
+ else
69
+ 'fields/addfield'
70
+ end
71
+ end
72
+
73
+ def create
74
+ @site.query :post, create_uri, { parameters: @data }.to_json
75
+ end
76
+ end
77
+
78
+ class FieldUser < FieldLookup
79
+ field 'AllowDisplay'
80
+ field 'Presence'
81
+ field 'SelectionGroup'
82
+ field 'SelectionMode'
83
+ end
84
+
85
+ class FieldMultiChoice < Field
86
+ field 'Choices'
87
+ field 'FillInChoice'
88
+ end
89
+
90
+ class FieldChoice < Field
91
+ field 'EditFormat'
92
+ end
93
+
94
+ class FieldRatingScale < Field
95
+ field 'GridEndNumber'
96
+ field 'GridNAOptionText'
97
+ field 'GridStartNumber'
98
+ field 'GridTextRangeAverage'
99
+ field 'GridTextRangeHigh'
100
+ field 'GridTextRangeLow'
101
+ end
102
+
103
+ class FieldMultiLineText < Field
104
+ field 'AllowHyperlink'
105
+ field 'AppendOnly'
106
+ field 'NumberOfLines'
107
+ field 'RestrictedMode'
108
+ field 'RichText'
109
+ end
110
+
111
+ class FieldNumber < Field
112
+ field 'MaximumValue'
113
+ field 'MinimumValue'
114
+ end
115
+
116
+ class FieldCurrency < FieldNumber
117
+ field 'CurrencyLocaleId'
118
+ end
119
+
120
+ class FieldText < Field
121
+ field 'MaxLength'
122
+ end
123
+
124
+ class FieldUrl < Field
125
+ end
126
+ end
@@ -0,0 +1,69 @@
1
+ require 'open-uri'
2
+
3
+ module Sharepoint
4
+ class Folder < Sharepoint::Object
5
+ include Sharepoint::Type
6
+ sharepoint_resource get_from_name: 'GetFolderByServerRelativeUrl'
7
+
8
+ field 'WelcomePage'
9
+ field 'UniqueContentTypeOrder'
10
+
11
+ method :recycle
12
+
13
+ def file_from_name name
14
+ @site.query :get, "#{__metadata['uri']}/files/getbyurl('#{URI::encode(name.to_s)}')"
15
+ end
16
+
17
+ def add_file name, content
18
+ uri = "#{__metadata['uri']}/files/add(overwrite=true,url='#{URI::encode(name.to_s)}')"
19
+ @site.query :post, uri, content
20
+ end
21
+
22
+ def add_folder name
23
+ uri = "#{__metadata['uri']}/folders"
24
+ body = { '__metadata' => { 'type' => 'SP.Folder' }, 'ServerRelativeUrl' => name.to_s }
25
+ @site.query :post, uri, body.to_json
26
+ end
27
+ end
28
+
29
+ class File < Sharepoint::Object
30
+ include Sharepoint::Type
31
+ belongs_to :folder
32
+ sharepoint_resource getter: 'GetFileByServerRelativeUrl', no_root_collection: true
33
+
34
+ method :approve, default_params: ({ comment: '' })
35
+ method :deny, default_params: ({ comment: '' })
36
+ method :checkin, default_params: ({ comment: '', checkintype: 0 })
37
+ method :checkout
38
+ method :undo_checkout
39
+ method :copy_to, default_params: ({ overwrite: true })
40
+ method :move_to, default_params: ({ flags: 9 })
41
+ method :get_limited_webpart_manager, default_params: ({ scope: 0 }), http_method: :get
42
+ method :download, endpoint: '$value', http_method: :get, skip_json: true
43
+ method :upload, endpoint: '$value', http_method: :put
44
+ method :publish, default_params: ({ comment: '' })
45
+ method :unpublish, default_params: ({ comment: '' })
46
+ method :recycle
47
+
48
+ def upload_from_file filename
49
+ content = String.new
50
+ ::File.open filename, 'rb' do |file|
51
+ line = nil
52
+ content += line while line = file.gets
53
+ end
54
+ upload content
55
+ end
56
+
57
+ def download_to_file filename
58
+ content = download
59
+ ::File.open filename, "w:#{content.encoding.name}" do |file|
60
+ file.write content
61
+ end
62
+ end
63
+ end
64
+
65
+ class FileVersion < Sharepoint::Object
66
+ include Sharepoint::Type
67
+ belongs_to :file
68
+ end
69
+ end
@@ -0,0 +1,30 @@
1
+ require 'curb'
2
+ require 'cgi'
3
+
4
+ module Sharepoint
5
+ module HttpAuth
6
+ class Session
7
+ attr_accessor :site
8
+ attr_reader :user, :password
9
+
10
+ def initialize site
11
+ @site = site
12
+ end
13
+
14
+ def authenticate user, password
15
+ @user = user
16
+ @password = password
17
+ end
18
+
19
+ def cookie
20
+ String.new
21
+ end
22
+
23
+ def curl curb
24
+ curb.http_auth_types = :ntlm
25
+ curb.username = @user
26
+ curb.password = @password
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,235 @@
1
+ module Sharepoint
2
+ LIST_TEMPLATE_TYPE = {
3
+ InvalidType: -1,
4
+ NoListTemplate: 0,
5
+ GenericList: 100,
6
+ DocumentLibrary: 101,
7
+ Survey: 102,
8
+ Links: 103,
9
+ Announcements: 104,
10
+ Contacts: 105,
11
+ Events: 106,
12
+ Tasks: 107,
13
+ DiscussionBoard: 108,
14
+ PictureLibrary: 109,
15
+ DataSources: 110,
16
+ UserInformation: 112,
17
+ WebPartCatalog: 113,
18
+ ListTemplateCatalog: 114,
19
+ XMLForm: 115,
20
+ MasterPageCatalog: 116,
21
+ NoCodeWorkflows: 117,
22
+ WorkflowProcess: 118,
23
+ WebPageLibrary: 119,
24
+ CustomGrid: 120,
25
+ SolutionCatalog: 121,
26
+ NoCodePublic: 122,
27
+ ThemeCatalog: 123,
28
+ DesignCatalog: 124,
29
+ AppDataCatalog: 125,
30
+ DataConnectionLibrary: 130,
31
+ WorkflowHistory: 140,
32
+ GanttTask: 150,
33
+ HelpLibrary: 151,
34
+ AccessRequest: 160,
35
+ TasksWithTimelineAndHierarchy: 171,
36
+ MaintenanceLogs: 175,
37
+ Meetings: 200,
38
+ Agenda: 201,
39
+ MeetingUser: 202,
40
+ Decision: 204,
41
+ MeetingObjective: 207,
42
+ TextBox: 210,
43
+ ThingsToBring: 211,
44
+ HomePageLibrary: 212,
45
+ Posts: 301,
46
+ Comments: 302,
47
+ Categories: 303,
48
+ Facility: 402,
49
+ Whereabouts: 403,
50
+ CallTrack: 404,
51
+ Circulation: 405,
52
+ Timecard: 420,
53
+ Hollidays: 421,
54
+ IMEDic: 499,
55
+ ExternalList: 600,
56
+ MySiteDocumentLibrary: 700,
57
+ IssueTracking: 1100,
58
+ AdminTasks: 1200,
59
+ HealthRules: 1220,
60
+ HealthReports: 1221,
61
+ DeveloperSiteDraftApps: 1230,
62
+ MicroFeed: 544 # Is undefined, but used by Sharepoint Online
63
+ }
64
+
65
+ module VtiBin
66
+ def self.translate_field_names input
67
+ return input unless defined? VtiDisplayNameDictionary
68
+ hash = Hash.new
69
+ input.keys.each do |key|
70
+ if VtiDisplayNameDictionary.keys.include? key
71
+ hash[VtiDisplayNameDictionary[key]] = input[key]
72
+ else
73
+ hash[key] = input[key]
74
+ end
75
+ end
76
+ hash
77
+ end
78
+ end
79
+
80
+ class List < Sharepoint::Object
81
+ include Sharepoint::Type
82
+ sharepoint_resource get_from_name: 'lists/getbytitle'
83
+
84
+ def find_items options = {}
85
+ @site.query :get, (make_item_filter options)
86
+ end
87
+
88
+ def item_count
89
+ @site.query :get, "#{__metadata['id']}/ItemCount"
90
+ end
91
+
92
+ def add_item attributes
93
+ attributes['__metadata'] ||= Hash.new
94
+ attributes['__metadata']['type'] ||= list_item_entity_type_full_name
95
+ @site.query :post, item_uri, attributes.to_json
96
+ end
97
+
98
+ def add_folder path, attributes
99
+ path = path.gsub(/\/*$/, '') # remove slashes at the end of the path
100
+ site_url = "#{@site.protocole}://#{@site.server_url}/"
101
+ action = "#{site_url}_vti_bin/listdata.svc/#{self.title}"
102
+ path = root_folder.server_relative_url + '/' + path
103
+ attributes['ContentTypeID'] ||= '0x01200059042D1A09191046851FA83D5B89816A'
104
+ attributes['Path'] ||= path
105
+ payload = VtiBin.translate_field_names(attributes).to_json
106
+ # Create the item using _vti_bin api
107
+ response = @site.query :post, action, payload, true do |curl|
108
+ curl.headers['Slug'] = "#{path}/#{attributes['Title']}|0x0120"
109
+ end
110
+ response = JSON.parse response
111
+ unless response['d'].nil?
112
+ # Fetch the item we just created using the REST api
113
+ item_id = response['d']['ID']
114
+ @site.query :get, "#{site_url}_api/#{__metadata['id']}/items(#{item_id})"
115
+ else
116
+ raise Sharepoint::SPException.new response, action, payload
117
+ end
118
+ end
119
+
120
+ field 'BaseTemplate', access: [ :read, :initialize ], default: LIST_TEMPLATE_TYPE[:GenericList]
121
+ field 'ContentTypesEnabled', default: true
122
+ field 'DefaultContentApprovalWorkflowId'
123
+ field 'DefaultDisplayFormUrl'
124
+ field 'DefaultEditFormUrl'
125
+ field 'DefaultNewFormUrl'
126
+ field 'Description'
127
+ field 'Direction'
128
+ field 'DocumentTemplateUrl'
129
+ field 'DraftVersionVisibility', default: 1
130
+ field 'EnableAttachments', default: false
131
+ field 'EnableFolderCreation', default: true
132
+ field 'EnableMinorVersions', default: true
133
+ field 'EnableModeration', default: true
134
+ field 'EnableVersioning', default: true
135
+ field 'ForceCheckout', default: false
136
+ field 'Hidden', default: false
137
+ field 'IrmEnabled', default: false
138
+ field 'IrmExpire', default: false
139
+ field 'IrmReject', default: false
140
+ field 'IsApplicationList', default: false
141
+ field 'MultipleDataList', default: false
142
+ field 'NoCrawl', default: false
143
+ field 'OnQuickLaunch', default: false
144
+ field 'Title'
145
+ field 'ValidationFormula'
146
+ field 'ValidationMessage'
147
+
148
+ private
149
+ def item_uri
150
+ url = @data['Items']['__deferred']
151
+ url = url['uri'] if url.class != String
152
+ url
153
+ end
154
+
155
+ def make_item_filter options = {}
156
+ url = item_uri
157
+ has_options = false
158
+ options.each do |key,value|
159
+ url += if has_options then '&' else '?' end
160
+ url += "$#{key}=#{URI::encode value.to_s}"
161
+ has_options = true
162
+ end
163
+ url
164
+ end
165
+ end
166
+
167
+ class ListItem < Sharepoint::Object
168
+ include Sharepoint::Type
169
+ belongs_to :list
170
+
171
+ method :break_role_inheritance, default_params: ({ copyroleassignements: true })
172
+ method :recycle
173
+ method :reset_role_inheritance
174
+ method :validate_update_item_list
175
+ end
176
+
177
+ class View < Sharepoint::Object
178
+ include Sharepoint::Type
179
+ belongs_to :list
180
+
181
+ field 'Aggregations'
182
+ field 'AggregationsStatus'
183
+ field 'ContentTypeId'
184
+ field 'DefaultView'
185
+ field 'DefaultViewForContentType'
186
+ field 'EditorModified'
187
+ field 'Formats'
188
+ field 'Hidden'
189
+ field 'IncludeRootFolder'
190
+ #field 'JsLink'
191
+ field 'ListViewXml'
192
+ field 'Method'
193
+ field 'MobileDefaultView'
194
+ field 'MobileView'
195
+ field 'Paged'
196
+ field 'RowLimit'
197
+ field 'Scope'
198
+ field 'Title'
199
+ field 'Toolbar'
200
+ field 'ViewData'
201
+ field 'ViewJoins'
202
+ field 'ViewProjectedFields'
203
+ field 'ViewQuery'
204
+
205
+ method :break_role_inheritance, default_params: ({ copyroleassignements: true })
206
+ method :recycle
207
+ method :render_list_form_data
208
+ method :reserve_list_item_id
209
+ end
210
+
211
+ class ViewFieldCollection < Sharepoint::Object
212
+ include Sharepoint::Type
213
+ sharepoint_resource
214
+ belongs_to :view
215
+
216
+ def add_view_field name
217
+ @site.query :post, "#{__metadata['uri']}/addviewfield('#{URI.encode name}')"
218
+ end
219
+
220
+ def move_view_field_to name, index
221
+ @site.query :post, "#{__metadata['uri']}/moveviewfieldto", {
222
+ field: name,
223
+ index: index
224
+ }.to_json
225
+ end
226
+
227
+ def remove_all_view_fields
228
+ @site.query :post, "#{__metadata['uri']}/removeallviewfields"
229
+ end
230
+
231
+ def remove_view_field name
232
+ @site.query :post, "#{__metadata['uri']}/removeviewfield('#{URI.encode name}')"
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,179 @@
1
+ require 'sharepoint-properties'
2
+ require 'sharepoint-stringutils'
3
+
4
+ module Sharepoint
5
+ class Site
6
+ end
7
+
8
+ class Object < Sharepoint::ObjectProperties
9
+ class << self
10
+ attr_accessor :fields
11
+
12
+ def fields
13
+ @fields ||= []
14
+ if self.superclass != Sharepoint::Object
15
+ self.superclass.fields | @fields
16
+ else
17
+ @fields
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def field name, options = {}
24
+ options[:access] ||= [ :read, :write ]
25
+ @fields ||= []
26
+ @fields << { name: name, access: options[:access], default: options[:default] }
27
+ end
28
+
29
+ def method name, method_params = {}
30
+ method_params[:http_method] ||= :post
31
+ method_params[:endpoint] ||= name.to_s.camelize
32
+ method_params[:default_params] ||= Hash.new
33
+ define_method name, ->(params = Hash.new) do
34
+ action = "#{__metadata['uri']}/#{method_params[:endpoint]}"
35
+ body = nil
36
+ # Set default parameters
37
+ (method_params[:default_params].each do |key,value|
38
+ params[key] == value if params[key].nil?
39
+ end)
40
+ if (method_params[:http_method] == :get) and (params.class < Hash) and (params.keys.count > 0)
41
+ # if method is get, Fill action with parameters
42
+ action += '('
43
+ (params.each do |key,value|
44
+ action += ',' unless params.keys.first == key
45
+ action += key + '='
46
+ action += (if (value.class < String) or (value.class < Symbol)
47
+ "'#{(URI.encode value.gsub("'", %q(\\\')))}'"
48
+ else
49
+ value
50
+ end)
51
+ end)
52
+ action += ')'
53
+ else
54
+ # if method is post, send parameters in the body
55
+ body = (params.class < Hash ? params.to_json : params)
56
+ end
57
+ # Call action
58
+ @site.query method_params[:http_method], action, body, method_params[:skip_json]
59
+ end
60
+ end
61
+
62
+ def sharepoint_resource options = {}
63
+ options[:method_name] ||= (self.name).split('::').last.downcase + 's'
64
+ options[:getter] ||= options[:method_name]
65
+ options[:get_from_name] ||= options[:getter]
66
+ Sharepoint::Site.send :define_method, options[:method_name] do
67
+ self.query :get, options[:getter].to_s
68
+ end unless options[:no_root_collection] == true
69
+ Sharepoint::Site.send :define_method, (self.name).split('::').last.downcase do |id|
70
+ if id =~ /^[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}$/
71
+ self.query :get, "#{options[:getter]}(guid'#{id}')"
72
+ else
73
+ self.query :get, "#{options[:get_from_name]}('#{URI.encode id}')"
74
+ end
75
+ end
76
+ end
77
+
78
+ def belongs_to resource_name
79
+ resource_name = resource_name.to_s
80
+ class_name = (self.name).split('::').last.downcase
81
+ method_name = class_name.pluralize
82
+ define_singleton_method "all_from_#{resource_name}" do |resource|
83
+ resource.site.query :get, "#{resource.__metadata['uri']}/#{method_name}"
84
+ end
85
+ define_singleton_method "get_from_#{resource_name}" do |resource, name|
86
+ resource.site.query :get, "#{resource.__metadata['uri']}/#{method_name}('#{URI.encode name}')"
87
+ end
88
+ define_method "create_uri" do
89
+ unless self.parent.nil?
90
+ "#{self.parent.__metadata['uri']}/#{method_name}"
91
+ else
92
+ method_name
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ attr_accessor :parent
99
+
100
+ def initialize site, data
101
+ @parent = nil
102
+ super site, data
103
+ end
104
+
105
+ def guid
106
+ return @guid unless @guid.nil?
107
+ __metadata['id'].scan(/guid'([^']+)'/) do ||
108
+ @guid = $1
109
+ break
110
+ end
111
+ @guid
112
+ end
113
+
114
+ def reload
115
+ @site.query :get, __metadata['uri']
116
+ end
117
+
118
+ def save
119
+ if @data['__metadata'].nil? or @data['__metadata']['id'].nil?
120
+ create
121
+ elsif @updated_data.keys.count > 0
122
+ update
123
+ end
124
+ end
125
+
126
+ def destroy
127
+ @site.query :post, resource_uri do |curl|
128
+ curl.headers['X-HTTP-Method'] = 'DELETE'
129
+ curl.headers['If-Match'] = __metadata['etag']
130
+ end
131
+ end
132
+
133
+ def copy new_object = nil
134
+ updating = !new_object.nil?
135
+ new_object ||= self.class.new @site
136
+ self.class.fields.each do |field|
137
+ next unless @data.keys.include? field[:name].to_s
138
+ next if (field[:access] & [ :write, :initialize ]).count == 0
139
+ value = @data[field[:name].to_s]
140
+ if updating == false
141
+ new_object.data[field[:name].to_s] = value
142
+ elsif new_object.data[field[:name].to_s] != value
143
+ new_object.updated_data[field[:name].to_s] = value
144
+ end
145
+ end
146
+ new_object
147
+ end
148
+
149
+ private
150
+ def sharepoint_typename
151
+ if self.is_a?(Sharepoint::GenericSharepointObject)
152
+ @generic_type_name
153
+ else
154
+ self.class.name.split('::').last
155
+ end
156
+ end
157
+
158
+ def resource_uri
159
+ @data['__metadata']['uri'].gsub(/^https:\/\/[^\/]+\/_api\/web\//i, '')
160
+ end
161
+
162
+ def create_uri
163
+ sharepoint_typename.downcase.pluralize
164
+ end
165
+
166
+ def create
167
+ @site.query :post, create_uri, @data.to_json
168
+ end
169
+
170
+ def update
171
+ @updated_data['__metadata'] ||= @data['__metadata']
172
+ @site.query :post, resource_uri, @updated_data.to_json do |curl|
173
+ curl.headers['X-HTTP-Method'] = 'MERGE'
174
+ curl.headers['If-Match'] = __metadata['etag']
175
+ end
176
+ @updated_data = Hash.new
177
+ end
178
+ end
179
+ end
@@ -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.make_object_from_data 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,131 @@
1
+ require 'curb'
2
+ require 'json'
3
+ require 'sharepoint-session'
4
+ require 'sharepoint-object'
5
+ require 'sharepoint-types'
6
+
7
+ module Sharepoint
8
+ class SPException < Exception
9
+ def initialize data, uri = nil, body = nil
10
+ @data = data['error']
11
+ @uri = uri
12
+ @body = body
13
+ end
14
+
15
+ def lang ; @data['message']['lang'] ; end
16
+ def message ; @data['message']['value'] ; end
17
+ def code ; @data['code'] ; end
18
+ def uri ; @uri ; end
19
+ def request_body ; @body ; end
20
+ end
21
+
22
+ class Site
23
+ attr_reader :server_url
24
+ attr_accessor :url, :protocole
25
+ attr_accessor :session
26
+ attr_accessor :name
27
+ attr_accessor :verbose
28
+
29
+ def initialize server_url, site_name
30
+ @server_url = server_url
31
+ @name = site_name
32
+ @url = "#{@server_url}/#{@name}"
33
+ @session = Session.new self
34
+ @web_context = nil
35
+ @protocole = 'https'
36
+ @verbose = false
37
+ end
38
+
39
+ def authentication_path
40
+ "#{@protocole}://#{@server_url}/_forms/default.aspx?wa=wsignin1.0"
41
+ end
42
+
43
+ def api_path uri
44
+ "#{@protocole}://#{@url}/_api/web/#{uri}"
45
+ end
46
+
47
+ def filter_path uri
48
+ uri
49
+ end
50
+
51
+ def context_info
52
+ query :get, ''
53
+ end
54
+
55
+ # Sharepoint uses 'X-RequestDigest' as a CSRF security-like.
56
+ # The form_digest method acquires a token or uses a previously acquired
57
+ # token if it is still supposed to be valid.
58
+ def form_digest
59
+ if @web_context.nil? or (not @web_context.is_up_to_date?)
60
+ @getting_form_digest = true
61
+ @web_context = query :post, "#{@protocole}://#{@server_url}/_api/contextinfo"
62
+ @getting_form_digest = false
63
+ end
64
+ @web_context.form_digest_value
65
+ end
66
+
67
+ def query method, uri, body = nil, skip_json=false, &block
68
+ uri = if uri =~ /^http/ then uri else api_path(uri) end
69
+ arguments = [ uri ]
70
+ arguments << body if method != :get
71
+ result = Curl::Easy.send "http_#{method}", *arguments do |curl|
72
+ curl.headers["Cookie"] = @session.cookie
73
+ curl.headers["Accept"] = "application/json;odata=verbose"
74
+ if method != :get
75
+ curl.headers["Content-Type"] = curl.headers["Accept"]
76
+ curl.headers["X-RequestDigest"] = form_digest unless @getting_form_digest == true
77
+ end
78
+ curl.verbose = @verbose
79
+ @session.send :curl, curl unless not @session.methods.include? :curl
80
+ block.call curl unless block.nil?
81
+ end
82
+
83
+ unless skip_json || (result.body_str.nil? || result.body_str.empty?)
84
+ begin
85
+ data = JSON.parse result.body_str
86
+ raise Sharepoint::SPException.new data, uri, body unless data['error'].nil?
87
+ make_object_from_response data
88
+ rescue JSON::ParserError => e
89
+ raise Exception.new("Exception with body=#{body}, e=#{e.inspect}, #{e.backtrace.inspect}, response=#{result.body_str}")
90
+ end
91
+ else
92
+ result.body_str
93
+ end
94
+ end
95
+
96
+ def make_object_from_response data
97
+ if data['d']['results'].nil?
98
+ data['d'] = data['d'][data['d'].keys.first] if data['d']['__metadata'].nil?
99
+ if not data['d'].nil?
100
+ make_object_from_data data['d']
101
+ else
102
+ nil
103
+ end
104
+ else
105
+ array = Array.new
106
+ data['d']['results'].each do |result|
107
+ array << (make_object_from_data result)
108
+ end
109
+ array
110
+ end
111
+ end
112
+
113
+ # Uses sharepoint's __metadata field to solve which Ruby class to instantiate,
114
+ # and return the corresponding Sharepoint::Object.
115
+ def make_object_from_data data
116
+ type_name = data['__metadata']['type'].gsub(/^SP\./, '')
117
+ type_parts = type_name.split '.'
118
+ type_name = type_parts.pop
119
+ constant = Sharepoint
120
+ type_parts.each do |part| constant = constant.const_get part end
121
+
122
+ klass = constant.const_get type_name rescue nil
123
+ if klass
124
+ klass.new self, data
125
+ else
126
+ Sharepoint::GenericSharepointObject.new type_name, self, data
127
+ end
128
+ end
129
+ end
130
+ end
131
+
@@ -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,83 @@
1
+ require 'sharepoint-object'
2
+
3
+ module Sharepoint
4
+ class UnsupportedType < ::Exception
5
+ attr_accessor :type_name
6
+
7
+ def initialize type_name
8
+ @type_name = type_name
9
+ end
10
+
11
+ def message
12
+ "unsupported type '#{@type_name}'"
13
+ end
14
+ end
15
+
16
+ module Type
17
+ def initialize site, data = nil
18
+ data ||= Hash.new
19
+ data['__metadata'] ||= {
20
+ 'type' => "SP.#{self.class.name.split('::').last}"
21
+ }
22
+ super site, data
23
+ end
24
+ end
25
+ end
26
+
27
+ require 'sharepoint-users'
28
+ require 'sharepoint-lists'
29
+ require 'sharepoint-files'
30
+ require 'sharepoint-fields'
31
+ require 'date'
32
+
33
+ module Sharepoint
34
+ ##
35
+ ## Sharepoint Management
36
+ ##
37
+ class Web < Sharepoint::Object
38
+ include Sharepoint::Type
39
+
40
+ field 'CustomMasterUrl'
41
+ field 'Description'
42
+ field 'EnableMinimalDownload'
43
+ field 'MasterUrl'
44
+ field 'QuickLaunchEnabled'
45
+ field 'SaveSiteAsTemplateEnabled'
46
+ field 'SyndicationEnabled'
47
+ field 'Title'
48
+ field 'TreeViewEnabled'
49
+ field 'UiVersion'
50
+ field 'UiVersionConfigurationEnabled'
51
+
52
+ method :apply_theme
53
+ method :break_role_inheritance, default_params: ({ clearsubscopes: true })
54
+ end
55
+
56
+ class ContextWebInformation < Sharepoint::Object
57
+ include Sharepoint::Type
58
+
59
+ def issued_time
60
+ strtime = (form_digest_value.split ',').last
61
+ DateTime.strptime strtime, '%d %b %Y %H:%M:%S %z'
62
+ end
63
+
64
+ def timeout_time
65
+ issued_time + Rational(form_digest_timeout_seconds, 86400)
66
+ end
67
+
68
+ def is_up_to_date?
69
+ DateTime.now < timeout_time
70
+ end
71
+ end
72
+
73
+ class FeatureCollection < Sharepoint::Object
74
+ include Sharepoint::Type
75
+ end
76
+
77
+ ##
78
+ ## Other types
79
+ ##
80
+ class PropertyValues < Sharepoint::Object
81
+ include Sharepoint::Type
82
+ end
83
+ end
@@ -0,0 +1,36 @@
1
+ module Sharepoint
2
+ class Group < Sharepoint::Object
3
+ include Sharepoint::Type
4
+ sharepoint_resource getter: :sitegroups
5
+ end
6
+
7
+ class User < Sharepoint::Object
8
+ include Sharepoint::Type
9
+ sharepoint_resource getter: :siteusers
10
+ belongs_to :group
11
+ end
12
+
13
+ class UserCustomAction < Sharepoint::Object
14
+ include Sharepoint::Type
15
+ sharepoint_resource
16
+ end
17
+
18
+ class RoleAssignment < Sharepoint::Object
19
+ include Sharepoint::Type
20
+ sharepoint_resource
21
+ end
22
+
23
+ class RoleDefinition < Sharepoint::Object
24
+ include Sharepoint::Type
25
+ end
26
+
27
+ class GenericSharepointObject < Sharepoint::Object
28
+ include Sharepoint::Type
29
+ sharepoint_resource
30
+
31
+ def initialize type_name, site, data
32
+ super site, data
33
+ @generic_type_name = type_name
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
2
+ <s:Header>
3
+ <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
4
+ <a:ReplyTo>
5
+ <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
6
+ </a:ReplyTo>
7
+ <a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>
8
+ <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
9
+ <o:UsernameToken>
10
+ <o:Username><%= @username %></o:Username>
11
+ <o:Password><%= @password %></o:Password>
12
+ </o:UsernameToken>
13
+ </o:Security>
14
+ </s:Header>
15
+ <s:Body>
16
+ <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
17
+ <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
18
+ <a:EndpointReference>'
19
+ <a:Address><%= @login_url %></a:Address>'
20
+ </a:EndpointReference>'
21
+ </wsp:AppliesTo>'
22
+ <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
23
+ <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
24
+ <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>'
25
+ </t:RequestSecurityToken>'
26
+ </s:Body>'
27
+ </s:Envelope>
28
+
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sharepoint-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Michael Martin Moro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ - - "<="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.8.6
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.8'
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.8.6
33
+ description: Client for Sharepoint's REST API
34
+ email: michael@unetresgrossebite.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - lib/sharepoint-fields.rb
40
+ - lib/sharepoint-files.rb
41
+ - lib/sharepoint-http-auth.rb
42
+ - lib/sharepoint-lists.rb
43
+ - lib/sharepoint-object.rb
44
+ - lib/sharepoint-properties.rb
45
+ - lib/sharepoint-ruby.rb
46
+ - lib/sharepoint-session.rb
47
+ - lib/sharepoint-stringutils.rb
48
+ - lib/sharepoint-types.rb
49
+ - lib/sharepoint-users.rb
50
+ - lib/soap/authenticate.xml.erb
51
+ homepage: https://github.com/Plaristote/sharepoint-ruby
52
+ licenses:
53
+ - BSD
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 2.5.1
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: sharepoint client
75
+ test_files: []