Class: SOLID::CommunityClient

Inherits:
Object
  • Object
show all
Defined in:
lib/solid-community-client-simple/communityclient.rb

Constant Summary collapse

VERSION =
"0.0.5"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(server:, username: ENV['SOLIDUSER'], password: ENV['SOLIDPASSWORD'], webid: nil) ⇒ SOLID::CommunityClient

Initialize the SOLID Community Client Object

Parameters:

  • server (String)

    The URL of the SOLID Pod you want to interact with

  • username (String) (defaults to: ENV['SOLIDUSER'])

    Your SOLID Pod username (defaults to ENV)

  • password (String) (defaults to: ENV['SOLIDPASSWORD'])

    Your SOLID Pod password (default to ENV)

  • webid (webid) (defaults to: nil)

    Your WebID that you use for authentication against that POD (default nil)



20
21
22
23
24
25
26
# File 'lib/solid-community-client-simple/communityclient.rb', line 20

def initialize(server:, username: ENV['SOLIDUSER'], password: ENV['SOLIDPASSWORD'], webid: nil)
  @username = username
  @password = password
  @server = server
  abort "can't proceed without usernme password" unless @username && @password
  @webid = webid
end

Instance Attribute Details

#account_metaObject

Returns the value of attribute account_meta.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def 
  @account_meta
end

#auth_stringObject

Returns the value of attribute auth_string.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def auth_string
  @auth_string
end

#credentials_urlObject

Returns the value of attribute credentials_url.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def credentials_url
  @credentials_url
end

#css_account_tokenObject

Returns the value of attribute css_account_token.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def 
  @css_account_token
end

#current_access_tokenObject

Returns the value of attribute current_access_token.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def current_access_token
  @current_access_token
end

#encoded_authObject

Returns the value of attribute encoded_auth.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def encoded_auth
  @encoded_auth
end

#login_metaObject

Returns the value of attribute login_meta.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def 
  @login_meta
end

#passwordObject

Returns the value of attribute password.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def password
  @password
end

#secretObject

Returns the value of attribute secret.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def secret
  @secret
end

#serverObject

Returns the value of attribute server.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def server
  @server
end

#tokenidObject

Returns the value of attribute tokenid.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def tokenid
  @tokenid
end

#usernameObject

Returns the value of attribute username.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def username
  @username
end

#webid_urlObject

Returns the value of attribute webid_url.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def webid_url
  @webid_url
end

#webidsObject

Returns the value of attribute webids.



8
9
10
# File 'lib/solid-community-client-simple/communityclient.rb', line 8

def webids
  @webids
end

Instance Method Details

#create_access_token(webid:, name: 'my-token') ⇒ String

Create the access token for this POD based in your WebID

Parameters:

  • webid (String)

    The URL of your WebID

  • name (String) (defaults to: 'my-token')

    some arbigtrary name to give your token (no sanity checking on the characters right now…)

Returns:

  • (String)

    The access token (this also sets the value of the SOLID::CommunityClient#current_access_token instance method)



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/solid-community-client-simple/communityclient.rb', line 110

def create_access_token(webid:, name: 'my-token')
  payload = { "name": name, "webId": webid }.to_json
  warn "", "", credentials_url, , payload, "", ""
  resp = RestClient::Request.new({
                                   method: :post,
                                   url: credentials_url,
                                   headers: { content_type: 'application/json', accept: 'application/json',
                                              authorization: "CSS-Account-Token #{}" },
                                   payload: payload
                                 }).execute
  # puts resp.body
  j = JSON.parse(resp.body)
  @tokenid = j['id'] # this is the ID that you see on the localhost:3000/yourpod Web page....
  @secret = j['secret']
  # concatenate tokenid and secret with a ":"
  @auth_string = "#{tokenid}:#{secret}"

  # BE CAREFUL!  Base64 encoders may add newline characters
  # pick an encoding method that does NOT do this!
  @encoded_auth = Base64.strict_encode64(auth_string)

  # where do I get a token?
  tokenurl = "#{server}.well-known/openid-configuration"
  resp = RestClient.get(tokenurl)
  j = JSON.parse(resp.body)
  token_endpoint = j['token_endpoint']
  warn "token endpoint #{token_endpoint}"

  payload = 'grant_type=client_credentials&scope=webid'
  resp = RestClient::Request.new({
                                   method: :post,
                                   url: token_endpoint,
                                   headers: {
                                     content_type: 'application/x-www-form-urlencoded',
                                     accept: 'application/json',
                                     authorization: "Basic #{encoded_auth}" # BASIC Auth
                                     #  'dpop': proof
                                   },
                                   payload: payload
                                 }).execute
  # puts resp.body
  j = JSON.parse(resp.body)
  @current_access_token = j['access_token']
end

#execute_with_proof(dpop:, data:, content_type:, current_access_token:) ⇒ RestClient::Response

Execute an interaction with a POD (e.g. read or create a resource or container)

Parameters:

  • dpop (SOLID::DPOP)

    The appropriate DPOP object (created by #prepare_dpop)

  • data (String)

    The data in your payload (can be “” but MUST be set, even for a GET!)

  • content_type (String)

    the MIME Content-type for the data

  • current_access_token (String)

    Your current access token (from #get_current_access_token)

Returns:

  • (RestClient::Response)

    This has no error checking at all… a failed request will cause a crash



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/solid-community-client-simple/communityclient.rb', line 211

def execute_with_proof(dpop:, data:, content_type:, current_access_token:)

  req = RestClient::Request.new({
                             method: dpop.method.downcase.to_sym,  #   htm: 'POST',    
                             url: dpop.url, #  htu: 'http://localhost:3000/markw/'
                             headers: {
                              # "Link": "<http://www.w3.org/ns/ldp/BasicContainer>; rel='type'",
                              content_type: content_type, # if you're sending turtle
                              # content_type: 'application/json',
                              accept: 'text/turtle',
                              #  slug: "container8",  # I am allowed to select my preferred resource name
                               authorization: "Bearer #{current_access_token}", # Note switch to Bearer auth!
                               'DPoP': dpop.proof
                             },
                             payload: data
                            })
  req.execute
end

#get_current_access_token(webid:, name: "my-token") ⇒ String

Either retrieve or create a valid access token. This should be used in preference to create_access_token, because it has logic to test the validity of the current token, and renew if it has a remaining lifespan of less than 30 seconds.

Parameters:

  • webid (String)

    Your WebID URL

  • name (String) (defaults to: "my-token")

    arbitrary string to call your token

Returns:

  • (String)

    The string of a valid token



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/solid-community-client-simple/communityclient.rb', line 163

def get_current_access_token(webid:, name: "my-token")
  create_access_token(webid: webid, name: name) unless current_access_token && !(current_access_token.empty?)

  # by default, I find the community server to make tokens that last for 10 minutes
  decoded_token = JWT.decode(current_access_token, nil, false)
  # Access the payload (claims)
  payload = decoded_token[0]
  # Print the entire payload
  puts "Token Payload: #{payload}"\
  # Check the expiry time
  if payload['exp']
    expiry_time = Time.at(payload['exp'])
    warn "Token Expiry Time: #{expiry_time} (#{expiry_time.utc})"
    if (expiry_time - Time.now) < 30
      puts "token will expire. Getting new one."
      create_access_token(webid: webid, name: name)
    else
      puts "The access token is still valid."
    end
  else
    puts "No expiration time (exp) claim found."
  end
  current_access_token
end

#get_webidsArray[String]

Retrieve the list of WebIDs that have access to this POD

Returns:

  • (Array[String])

    an array of URL strings that are the webids of all legitimate POD users



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/solid-community-client-simple/communityclient.rb', line 87

def get_webids
  resp = RestClient::Request.new({
                                   method: :get,
                                   url: webid_url,
                                   headers: {
                                     accept: 'application/json',
                                     authorization: "CSS-Account-Token #{}"
                                   }
                                 }).execute

  j = JSON.parse(resp.body)
  @webids = j['webIdLinks'].keys
  webids
end

#loginBoolean

Login to the POD. This creates additional accessors in the SOLID::CommunityClient Including:

#account_meta - this contains the parsed JSON from a GET on the PODs ./accout endpoint
#login_meta - this contains the parsed JSON from a GET on the PODs login_url (part of the #account_meta)
#credentials_url - the URL to be used to get credentials for the POD
#webid_url - the URL to call to get the list of WebIDs that have access to this POD

Returns:

  • (Boolean)

    Was login successful? (if not, this method will crash, for the moment! No error tolerance at all!)



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/solid-community-client-simple/communityclient.rb', line 39

def 
   = server + '.account/'

  resp = RestClient::Request.new({
                                   method: :get,
                                   url: ,
                                   headers: { accept: 'application/json' }
                                 }).execute

  @account_meta = SOLID::Account.new(json: JSON.parse(resp.body))
   = .

  # I take my username and password from the environment
  payload = { "email": username, "password": password }.to_json
  resp = RestClient::Request.new({
                                   method: :post,
                                   url: ,
                                   headers: {
                                     content_type: 'application/json',
                                     accept: 'application/json'
                                   },
                                   payload: payload
                                 }).execute

  @login_meta = SOLID::Login.new(json: JSON.parse(resp.body))
  @css_account_token = .

  # with the authoization, the return message for the /.account GET call is richer
  resp = RestClient::Request.new({
                                   method: :get,
                                   url: 'http://localhost:3000/.account/',
                                   headers: {
                                     accept: 'application/json',
                                     authorization: "CSS-Account-Token #{}"
                                   }
                                 }).execute

  @account_meta = SOLID::Account.new(json: JSON.parse(resp.body))
  @credentials_url = .credentials_url
  @webid_url = .webid_url
  true
end

#prepare_dpop(url:, method:) ⇒ SOLID::DPOP

Get the CommunityClient ready to do a specific DPoP interaction. DPoP requires the URL that you will interact with, adn the HTEP method you will use. You have to call this method prior to every new kind of interaction with the POD

Parameters:

  • url (String)

    The URL you will interact with

  • method (String)

    The HTTP Method (e.g. GET)

Returns:



196
197
198
# File 'lib/solid-community-client-simple/communityclient.rb', line 196

def prepare_dpop(url:, method:)
  SOLID::DPOP.new(url: url, method: method)
end