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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +19 -0
- data/LICENSE +27 -0
- data/README.md +167 -0
- data/lib/sharepoint-error.rb +3 -0
- data/lib/sharepoint-fields.rb +126 -0
- data/lib/sharepoint-files.rb +80 -0
- data/lib/sharepoint-http-auth.rb +30 -0
- data/lib/sharepoint-kerberos-auth.rb +30 -0
- data/lib/sharepoint-lists.rb +235 -0
- data/lib/sharepoint-object.rb +179 -0
- data/lib/sharepoint-properties.rb +96 -0
- data/lib/sharepoint-ruby.rb +152 -0
- data/lib/sharepoint-session.rb +92 -0
- data/lib/sharepoint-stringutils.rb +37 -0
- data/lib/sharepoint-types.rb +84 -0
- data/lib/sharepoint-users.rb +36 -0
- data/lib/sharepoint-version.rb +3 -0
- data/lib/soap/authenticate.xml.erb +28 -0
- data/proof-sharepoint-ruby.gemspec +24 -0
- metadata +86 -0
@@ -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
|