fidor_starter_kits 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a82c8968c4e81c82599966dd950785cb798a5285
4
- data.tar.gz: c8686d4dafd47ced1aebceb895a8bd4cee210ecb
3
+ metadata.gz: c225915b009fa0fb2bc2e50eb38d390bc6a9a0b7
4
+ data.tar.gz: 3547304f05e844378c9980252dd3ddf40717f4f2
5
5
  SHA512:
6
- metadata.gz: 329d6c28ef1c0acd2f1465fdb1cc9738c6a0fd56a2fe88726515f5d1c24ef685967edf79c68756ce18f77b51555d77a7f79cbcd945fcf9d6092e70045b1df205
7
- data.tar.gz: b56cfeabdd6c75febe3b7fe387a5de57bd6c74bab7ccff50f7bfb6a67bd8dfefbd072b7a6b48d5e5892096942f75331187a2cef0251b132c62ca796f5f1629d2
6
+ metadata.gz: b6fa01c0bdf645fe69e7bfab33b2ee7ac2a7ede087f7ef71dbefef88efeb80fb674554b40cf10f80b4d9dc9b808434c5372e7059c6d8ca52cf603e19e1e0dbe7
7
+ data.tar.gz: 21888227a82c338ad77845ae9c1d0cce1eb84a71202c0907eed5bcb8f351fbe5eefb6e00221144f87455c2030a87e4172bc2efd6623c0415956269bb5bb4c266
data/.gitignore CHANGED
@@ -22,3 +22,4 @@ secrets.yml
22
22
  .ruby-gemset
23
23
  *.swp
24
24
  node_modules
25
+ .DS_Store
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog Fidor Admin API Schema
2
+ See [commit messages](https://github.com/fidor/fidor_starter_kits/commits/) for details.
3
+
4
+
5
+ ##2014-12
6
+
7
+ * make starter kits use all required oauth params: state, response_type, grant_type
8
+
9
+
10
+ ##2014-11
11
+
12
+ * init with plain oauth examples for php, ruby, nodejs, golang, java
data/README.md CHANGED
@@ -1,12 +1,18 @@
1
1
  # FidorStarterKits - BETA
2
2
 
3
- This repo features fidor application examples. The apps use oAuth to
4
- authenticate with fidor, so you must register an app at fidor first to get your
5
- personal app credentials.
6
- Afterwards just copy one of the /starter_kits, add your app credentials to the
7
- example source and start to play.
3
+ This repo features Fidor application examples. The apps use oAuth to
4
+ authenticate with fidor, so you must register an app at fidor first to get your
5
+ personal app credentials.
8
6
 
9
- For Ruby Heros, this repo is also available as ruby gem and provides a tiny
7
+ For a quick start the Fidor App Manager(where you register your app) features a
8
+ 'create&download app' method. This creates a new app, writes the required oauth
9
+ information into the source files and delivers the example as zipped download.
10
+
11
+ If you choose the manual way to explore our example apps, simply checkout this
12
+ repo, register and app and add the required credentials, URL's to the sources.
13
+
14
+
15
+ For Ruby Heros, this repo is also available as ruby gem and provides a tiny
10
16
  helper for app creation, see Install & Usage.
11
17
 
12
18
 
@@ -38,7 +44,7 @@ Create a zipped app with credentials and the fidor url:
38
44
  fidor_oauth_url: 'https://fidor-oauth-url.de/oauth',
39
45
  fidor_api_url: 'https://fidor-api-url.de/api_sandbox'
40
46
  }
41
-
47
+
42
48
  zip_file_path = FidorStarterKits.build(opts)
43
49
  # => /tmp/sinatra_plain-xyz/sinatra_plain.zip
44
50
  # => mv / cp / download is up to you babee
@@ -46,9 +52,10 @@ Create a zipped app with credentials and the fidor url:
46
52
  ```
47
53
  ## Build your own starter kit
48
54
 
49
- As a quickstart for new developers we zip and download the examples in our
50
- application manager. Before the following placeholders inside in your main
51
- example.xy file are substituted with the according values from the app:
55
+ As a quickstart for new developers we zip and download the examples in our
56
+ application manager. Before the following placeholders inside in your main
57
+ example.xy file are substituted with the according values from the app
58
+ (client_id/secret) and the values in .fidor_meta.json:
52
59
 
53
60
  <APP_URL> # default http://localhost:8000/example.php
54
61
  <CLIENT_ID>
@@ -56,7 +63,7 @@ example.xy file are substituted with the according values from the app:
56
63
  <FIDOR_OAUTH_URL> # e.g https://fidor.com/oauth
57
64
  <FIDOR_API_URL>
58
65
 
59
- So just add those to example.[rb, php, ..] and see existing examples and specs
66
+ So just add those to example.[rb, php, ..] and see existing examples and specs
60
67
  for a reference.
61
68
 
62
69
  ## Contributing
@@ -1,3 +1,3 @@
1
1
  module FidorStarterKits
2
- VERSION = '0.2.3'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -16,6 +16,7 @@ import (
16
16
  "fmt"
17
17
  "net/http"
18
18
  "net/url"
19
+ "errors"
19
20
  )
20
21
 
21
22
  // The following sections define the settings you require for the
@@ -23,17 +24,17 @@ import (
23
24
 
24
25
  // app ID and secret, can be found in this apps "Details" page in the
25
26
  // AppManager.
26
- var client_id = "<CLIENT_ID>"
27
+ var client_id = "<CLIENT_ID>"
27
28
  var client_secret = "<CLIENT_SECRET>"
28
29
 
29
30
  // Fidor's OAuth Endpoint (this changes between Sandbox and Production)
30
31
  var fidor_oauth_url = "<FIDOR_OAUTH_URL>" // e.g https://fidor.com/api_sandbox/oauth
31
32
  // The OAuth Endpoint this App provides
32
- var oauth_cb_url = "<APP_URL>"
33
+ var oauth_cb_url = "<APP_URL>"
33
34
 
34
35
  // The URL of the Fidor API (this changes between Sandbox and
35
36
  // Production)
36
- var fidor_api_url = "<FIDOR_API_URL>" // e.g https://fidor.com/api_sandbox vs /api
37
+ var fidor_api_url = "<FIDOR_API_URL>" // e.g https://fidor.com/api_sandbox vs /api
37
38
 
38
39
 
39
40
 
@@ -72,7 +73,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
72
73
  } else {
73
74
  // we don't have an oauth `code` yet, so we need to
74
75
  // redirect the user to the OAuth provider to get one ...
75
- oauth_url := fmt.Sprintf("%s/authorize?client_id=%s&redirect_uri=%s",
76
+ oauth_url := fmt.Sprintf("%s/authorize?client_id=%s&state=123&response_type=code&redirect_uri=%s",
76
77
  fidor_oauth_url,
77
78
  client_id,
78
79
  url.QueryEscape(oauth_cb_url))
@@ -99,11 +100,17 @@ func retrieveTokenFromCode(code string) (token string, err error) {
99
100
  "client_id": {client_id},
100
101
  "client_secret": {client_secret},
101
102
  "code": {code},
103
+ "redirect_uri": {url.QueryEscape(oauth_cb_url)},
104
+ "grant_type": {"autorization_code"}
102
105
  }
103
106
  // Call API
104
107
  if resp, err := http.PostForm(tokenUrl, tokenPayload); err != nil {
108
+ println(err)
105
109
  return "", err
106
110
  } else {
111
+ if resp.StatusCode != 200 {
112
+ return "", errors.New(resp.Status)
113
+ }
107
114
  // if successful, pick the access_token out of the reply.
108
115
  var tokenResponse TokenResponse
109
116
  decoder := json.NewDecoder(resp.Body)
@@ -85,6 +85,7 @@ public class Example extends HttpServlet {
85
85
  .addParameter("redirect_uri", app_url)
86
86
  .addParameter("code", code)
87
87
  .addParameter("client_secret", client_secret)
88
+ .addParameter("grant_type", "authorization_token")
88
89
  .build();
89
90
 
90
91
  HttpResponse resp = httpClient.execute(req);
@@ -103,7 +104,7 @@ public class Example extends HttpServlet {
103
104
  }
104
105
 
105
106
  private String getCodeUrl() {
106
- return fidor_oauth_url + "/authorize?client_id=" + client_id + "&redirect_uri=" + app_url;
107
+ return fidor_oauth_url + "/authorize?client_id=" + client_id + "&redirect_uri=" + app_url +"&state=1234&response_type=authroization_code";
107
108
  }
108
109
 
109
110
  private String getAccountUrl(String token) {
@@ -14,119 +14,36 @@
14
14
  // manager an need to be transfered into the config below:
15
15
 
16
16
  var fidor_config = {
17
- app_port : 3141, // you might want to change this to match your app_url
18
- app_url : "<APP_URL>", // must also include the port e.g http://localhost:3141
17
+ app_port : 3001,
18
+ app_url : "<APP_URL>",
19
19
  client_id : "<CLIENT_ID>",
20
20
  client_secret : "<CLIENT_SECRET>",
21
21
  fidor_api_url : "<FIDOR_API_URL>"
22
22
  }
23
23
 
24
- // This app can be started by executing:
25
- //
26
- // node example.js
27
- //
28
- // Once the app is running, it can be accessed on:
29
- //
30
- // http://localhost:3141
31
- //
32
- // or on whatever port is configured above.
33
- //
34
- // The app checks whether an OAuth access token is available for the
35
- // user, if not, the user is redirected to the OAuth endpoint. After
36
- // successful authentication and authorization, the OAuth endpoint
37
- // redirects the user back to the app. Specifically to:
38
- //
39
- // http://localhost:3141/code
40
- //
41
- // This redirect will contain a query with the OAuth code. The app uses
42
- // this code to request an OAuth access-token. The retrieved token is stored with
43
- // the users session and is used to authenticate API requests.
44
-
45
- var http = require('http')
46
- var url = require('url')
47
- var querystring = require('querystring')
48
-
49
-
50
-
51
- // the actual call to the api.
52
- // @param accessToken : OAuth access_token for this user.
53
- // @param cb : callback function(err, transactions)
54
- function apiGetTransactions(accessToken, cb) {
55
- // URL endpoint to call to retrieve transactions.
56
- var tx_url = fidor_config.fidor_api_url+"/transactions?access_token="+ accessToken
57
-
58
- var get = http.get(tx_url, function(res){
59
- // response may come in numerous chunks, we need to collect
60
- // them and reassemble the entire answer when all data has
61
- // been retrieved.
62
- var data = new Buffer(0)
63
- res.on('data', function(chunk){
64
- data = Buffer.concat([data, chunk])
65
- })
66
- res.on('end', function() {
67
- if (res.statusCode !== 200) {
68
- cb(data.toString(), null)
69
- return
70
- }
71
- var d = JSON.parse(data)
72
- cb(d.error, d.data)
73
- })
74
- })
75
-
76
- get.on('error', function(e) {
77
- cb (e, null)
78
- })
79
- }
80
24
 
81
- //
82
- // The handle for the Apps /transactions endpoint.
83
- // @param request : http request object
84
- // @param response : http response
85
- //
86
- function getTransactions(request, response) {
87
- var cookie_token = getCookie(request, "oauth_token")
88
- // if we don't have a token for this user already, redirect
89
- // the user to the OAuth server
90
- if (!cookie_token) {
91
- var oauth_url = fidor_config.fidor_api_url+"/oauth/authorize?"+
92
- "client_id="+fidor_config.client_id+
93
- "&redirect_uri="+fidor_config.app_url+"/code"
94
- response.writeHead(307, {"location" : oauth_url})
95
- response.end()
96
- return
97
- }
98
-
99
- // trade in the key we stored in the cookie for the actual oauth token.
100
- var oauth_token = getToken(cookie_token)
101
-
102
- // call the api with the OAuth token:
103
- apiGetTransactions(oauth_token, function (err, transactions) {
104
- if (err) {
105
- // 500 Server Error in case of any problems
106
-
107
- console.log(">>>error")
108
- console.log(err)
109
- console.log("<<<error")
110
-
111
- response.writeHead(500, "Borked.")
112
- response.end("Borked.")
113
- return
114
- }
115
- // if everything went well, dump the received json.
116
- response.writeHead(200, {"Content-Type": "application/json; charset=utf-8"})
117
- response.write(JSON.stringify(transactions, null, " "))
118
- response.end()
119
-
120
- })
25
+ // redirect the user to the OAuth authorization endpoint with the
26
+ // following params:
27
+ // - client_id
28
+ // - state
29
+ // - response_type
30
+ // - redirect_uri
31
+ function redirect_to_oauth(response){
32
+ var redirect_uri = encodeURIComponent(fidor_config.app_url)
33
+ var oauth_url = fidor_config.fidor_api_url+
34
+ "/oauth/authorize?client_id="+fidor_config.client_id+
35
+ "&state=123&response_type=code&"+
36
+ "redirect_uri="+redirect_uri
37
+ response.writeHead(307, {"location" : oauth_url})
38
+ response.end()
39
+ return
121
40
  }
122
41
 
123
- // Trades the `code` we received from the OAuth server via client
124
- // redirect for an actual access_token. To do this, the code needs to be
125
- // send to the correct OAuth endppoint together with client_id and
126
- // client_secret.
127
- function getOAuthToken(code, cb) {
42
+ <// Execute a POST request against the OAUTH token endpoint
43
+ // in order to exchange: code, client_id, client_secret,
44
+ // rerdirect_uri and grant_type for an auth_token.
45
+ function retrieve_access_token_from_code( code, cb ) {
128
46
  var oauth_url = url.parse(fidor_config.fidor_api_url)
129
-
130
47
  // where to send the data ...
131
48
  var postOptions = {
132
49
  method: "POST",
@@ -139,7 +56,9 @@ function getOAuthToken(code, cb) {
139
56
  var postData = {
140
57
  code : code,
141
58
  client_id : fidor_config.client_id,
142
- client_secret : fidor_config.client_secret
59
+ client_secret : fidor_config.client_secret,
60
+ redirect_uri : encodeURIComponent(fidor_config.app_url),
61
+ grant_type : "authorization_code"
143
62
  }
144
63
  postData = querystring.stringify(postData)
145
64
 
@@ -153,6 +72,7 @@ function getOAuthToken(code, cb) {
153
72
 
154
73
  res.on('end', function() {
155
74
  var oauth_response = JSON.parse(data)
75
+ console.log(oauth_response)
156
76
  cb(oauth_response.error, oauth_response.access_token)
157
77
  })
158
78
  })
@@ -165,178 +85,62 @@ function getOAuthToken(code, cb) {
165
85
  token_request.end()
166
86
  }
167
87
 
168
- // handler we provide to handle our user being redirected back
169
- // to us from the OAuth server with the OAuth `code` in the
170
- // query of the url.
171
- function setOAuthToken(request, response) {
172
- var u = url.parse(request.url)
173
- var code = querystring.parse(u.query)["code"]
88
+ <
89
+ // Display a friendly message and links to the API Endpoints.
90
+ function renderWelcome(request, response, token) {
91
+ response.writeHead(200, {"Content-Type" : "text/html"})
92
+ var content = hello_template.replace(/{token}/g, token)
93
+ console.log(content)
94
+ console.log(token)
95
+ content = content.replace(/{api_uri}/g, fidor_config.fidor_api_url)
96
+ response.end(content)
97
+ }
98
+
99
+ // main http functionality
100
+ function listener (request, response) {
174
101
 
175
- // if the request does not contain a ?code=adsfasdfasdf
176
- // query, it's not valid.
177
- if (!code) {
178
- response.writeHead(400, "Bad Request")
102
+ var u = url.parse(request.url)
103
+ // reject everything but GET.
104
+ if (request.method !== "GET" || u.pathname !== "/") {
105
+ response.writeHead(403, "Forbidden")
179
106
  response.end()
180
107
  return
181
108
  }
182
-
183
- // exchange the code for a token ...
184
- getOAuthToken(code, function (err, token) {
185
- if (err) {
186
- console.log(">>>error")
187
- console.log(err)
188
- console.log("<<<error")
189
-
190
- response.writeHead(500, "Borked.")
191
- response.end("Borked.")
192
- return
193
- }
194
-
195
- // once we have the token, we store it in our app (see below). The
196
- // OAuth token is stored under a random key. We set this key in the
197
- // user's cookie in order to retrieve the access_token whenever we
198
- // may need it in the future without leaking the actual access_token
199
- // via the cookie
200
-
201
- var token_key = storeToken(token)
202
- setCookie(response, "oauth_token", token_key)
203
-
204
- // send the user back to the transactions url, this time, with a
205
- // valid access_token (via the cookie we just set)
206
- response.writeHead(307, {"location" : "/transactions"})
207
- response.end()
208
- })
209
- }
210
-
211
- // handler for all http requests to our app ...
212
- function listener(req, resp) {
213
- // reject everything but GET.
214
- if (req.method !== "GET") {
215
- resp.writeHead(403, "Forbidden")
109
+ var code = querystring.parse(u.query)["code"]
110
+ if (code) {
111
+ retrieve_access_token_from_code( code, function (err, token) {
112
+ if (err) {}
113
+ renderWelcome(request, response, token)
114
+ })
115
+ } else {
116
+ // we don't have an oauth `code` yet, so we need to
117
+ // redirect the user to the OAuth provider to get one ...
118
+ redirect_to_oauth(response)
119
+ return
216
120
  }
121
+ }
217
122
 
218
- var u = url.parse(req.url)
219
- switch (u.pathname) {
220
- case "/":
221
- console.log("start page ...")
222
- resp.writeHead(200, {"Content-Type" : "text/html"})
223
- resp.end(hello_template)
224
- break
225
- case "/transactions":
226
- console.log("transactions ...")
227
- getTransactions(req, resp)
228
- break
229
- // utility code to remove cookies in order to force OAuth.
230
- case "/code":
231
- console.log("received code redirect ...")
232
- setOAuthToken(req, resp)
233
- break
234
- case "/clear_all_cookies":
235
- console.log("clearing cookies")
236
- clearCookies(req, resp)
237
- resp.writeHead(307, {"location": "/"})
238
- resp.end()
239
- break
240
- case "/clear_cookie":
241
- console.log("clearing oauth cookie")
242
-
243
- var key = getCookie(req, "oauth_token")
244
- deleteToken(key)
245
-
246
- clearCookie(resp, "oauth_token")
247
- resp.writeHead(307, {"location": "/"})
248
- resp.end()
249
- break
250
- default:
251
- console.log("unknown: "+u.path)
252
- resp.end()
253
- }
123
+ // Execution starts here...
254
124
 
255
- }
125
+ var url = require("url")
126
+ var querystring = require("querystring")
127
+ var http = require("http")
256
128
 
257
129
  // start the server
258
130
  var server = http.createServer(listener)
259
131
  server.listen(fidor_config.app_port)
260
132
 
261
- console.log("listening on : "+fidor_config.app_port)
262
-
133
+ console.log("listening on : "+fidor_config.app_url)
263
134
 
264
135
 
265
136
  var hello_template = ""+
266
- "<DOCTYPE html>" +
267
- "<html>" +
268
- "<head></head>" +
269
- "<body>" +
270
- " <h1>Welcome to Transaction Getter!</h1>" +
271
- " <p><a href='/transactions'>Get Transactions</a></p>" +
272
- " <p><a href='/clear_cookie'>Clear Cookie</a></p>" +
273
- " <p><a href='/clear_all_cookies'>Clear All Cookies</a></p>" +
274
- "</body>" +
137
+ "<html>"+
138
+ "<head>"+
139
+ "</head>"+
140
+ "<body>"+
141
+ " <h1>Welcome!</h1>"+
142
+ " <i>retrieved <tt>access_token</tt>: {token} </i>"+
143
+ " <p><a href='{api_uri}/transactions?access_token={token}'>Transactions</a></p>"+
144
+ " <p><a href='{api_uri}/accounts?access_token={token}'>Accounts</a></p>"+
145
+ "</body>"+
275
146
  "</html>"
276
-
277
- // Code for setting and clearing cookies. Typically a framework would
278
- // handle the intricacies of cookie handling, but we opted not to
279
- // require any additional dependancies for this example so we need to
280
- // handle cookies manually...
281
-
282
- function setCookie(response, key, value) {
283
- response.setHeader("Set-Cookie", key+"="+value)
284
- }
285
-
286
- function clearCookie(response, key) {
287
- console.log("clearing: "+key)
288
- if (key.map) {
289
- key = key.map(function (k) {
290
- return k+"=delete; expires=Thu, 01 Jan 1970 00:00:00 GMT"
291
- })
292
- } else {
293
- key = key+"=delete; expires=Thu, 01 Jan 1970 00:00:00 GMT"
294
- }
295
- console.log(key)
296
- response.setHeader("Set-Cookie", key)
297
- }
298
-
299
- function clearCookies(request, response){
300
- clearCookie(response, Object.keys(getCookies(request)))
301
- }
302
-
303
-
304
- function getCookies(request) {
305
- var cookies = {}
306
- var c = request.headers["cookie"]
307
- if (c) {
308
- c.split(';').forEach(function(cookie){
309
- var c = cookie.split("=")
310
- cookies[c[0].trim()] = c[1]
311
- })
312
- }
313
- return cookies
314
- }
315
-
316
- function getCookie(request, key) {
317
- var cookies = getCookies(request)
318
- return cookies[key]
319
- }
320
-
321
- // token storage
322
-
323
-
324
- var tokens = {}
325
-
326
- function storeToken (token) {
327
- var key = ""
328
- for (var i =0 ; i!= 16; ++i) {
329
- key += Math.floor((Math.random() * 256)).toString(16)
330
- }
331
- tokens[key] = token
332
- return key
333
- }
334
- function getToken(key) {
335
- return tokens[key]
336
- }
337
-
338
- function deleteToken (key) {
339
- var tok = tokens[key]
340
- delete tokens[key]
341
- return tok
342
- }
@@ -12,7 +12,8 @@
12
12
  if(empty($code)) {
13
13
  $dialog_url = $fidor_oauth_url . "/authorize?" .
14
14
  "client_id=". $app_id .
15
- "&redirect_uri=" . urlencode($app_url);
15
+ "&redirect_uri=" . urlencode($app_url) .
16
+ "&state=1234&response_type=code";
16
17
 
17
18
  echo("<script> top.location.href='" . $dialog_url . "'</script>");
18
19
  }
@@ -22,7 +23,8 @@
22
23
  $data = array('client_id' => $app_id,
23
24
  'client_secret' => $app_secret,
24
25
  'code' => $code,
25
- 'redirect_uri' => urlencode($app_url)
26
+ 'redirect_uri' => urlencode($app_url),
27
+ 'grant_type' => 'authorization_code'
26
28
  );
27
29
  // use key 'http' even if you send the request to https://...
28
30
  $options = array(
@@ -13,7 +13,7 @@ get '/' do
13
13
 
14
14
  # 1. redirect to authorize url
15
15
  unless code = params["code"]
16
- dialog_url = "#{@fidor_oauth_url}/authorize?client_id=#{@client_id}&redirect_uri=#{CGI::escape(@app_url)}"
16
+ dialog_url = "#{@fidor_oauth_url}/authorize?client_id=#{@client_id}&redirect_uri=#{CGI::escape(@app_url)}&state=1234&response_type=code"
17
17
  redirect dialog_url
18
18
  end
19
19
 
@@ -21,9 +21,11 @@ get '/' do
21
21
  token_url = URI("#{@fidor_oauth_url}/token")
22
22
  # GET and parse access_token response json
23
23
  res = Net::HTTP.post_form(token_url, 'client_id' => @client_id,
24
- 'redirect_uri' => CGI::escape(@app_url),
25
- 'code' =>code,
26
- 'client_secret'=>@client_secret)
24
+ 'redirect_uri' => CGI::escape(@app_url),
25
+ 'code' =>code,
26
+ 'client_secret'=>@client_secret,
27
+ 'grant_type'=>'authorization_code')
28
+
27
29
  resp = ActiveSupport::JSON.decode(res.body)
28
30
 
29
31
  # GET current user
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fidor_starter_kits
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georg Leciejewski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-01 00:00:00.000000000 Z
11
+ date: 2014-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -75,6 +75,7 @@ extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
77
  - ".gitignore"
78
+ - CHANGELOG.md
78
79
  - Gemfile
79
80
  - LICENSE.txt
80
81
  - README.md