ass 0.0.21 → 0.0.24
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 +15 -0
- data/README.md +14 -2
- data/VERSION +1 -1
- data/ass.yml +3 -1
- data/lib/ass.rb +2 -404
- data/lib/ass/app.rb +277 -0
- data/lib/ass/conf.rb +165 -0
- metadata +57 -86
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZTA5ZjhkMzc5ZjFlNThiNzQ3YjExOGJiMTI4MmJmMDEzNjU3MjFlYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NTJjOGI2M2IyMGI2NTUyNTcwYTFiZTNmYjhkODBkZjUxOGVmMWNkZA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NmFhYjAzNGJmZTA4MmYwOGJkMDU2ZWFiMjMyZWM4YzUwNmZlNTdiYTcyYzk3
|
10
|
+
MmI5ZjYzMGY3Y2U0MTYwYzI4MWRjYjMxNWNiNTRkYWMxMTU1OTFjMDdkYmRl
|
11
|
+
MDQ5NmUwNjZiMTZjNGUxMWRmN2UyZjdlOTNiMjkwNWM2YzIxN2E=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZmI4NTFiMDdjNDQ4YTU2Y2ZjYWNiNTA1MDYzYTM1ODZkNWIxNTE2NGFiZTQ0
|
14
|
+
MTc5NmY1ZGJlYjg4YTViN2RkMTllYjNmNGQ2NjA3YWJlOWNmMTQ4M2U0OGY2
|
15
|
+
OGJlNjlkYjJkMzI0YjNmZDUxNmNjOGY2MGNkMjVkY2JmZTc4NmQ=
|
data/README.md
CHANGED
@@ -116,6 +116,9 @@ when you run 'ass' first time, it will generate 'ass.yml' config file under curr
|
|
116
116
|
timer: 0 # how often you run the cron job, unit: minute. when set with 0, means no cron job execute.
|
117
117
|
user: admin # admin username
|
118
118
|
pass: pass # admin password
|
119
|
+
flood: 1 # request time from same ip every one minute as Flood Attack
|
120
|
+
pempass: pempass # pem password
|
121
|
+
loglevel: info # logger level
|
119
122
|
apps:
|
120
123
|
- app1 ## appid you want to supprt APNS, ASS Server can give push notification support for many iOS apps, just list the appid here.
|
121
124
|
|
@@ -156,13 +159,11 @@ In AppDelegate file, add methods below to register device token
|
|
156
159
|
|
157
160
|
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
158
161
|
|
159
|
-
if ([((AppDelegate *) [[UIApplication sharedApplication] delegate]) checkNetwork1]) {
|
160
162
|
NSString *tokenAsString = [[[deviceToken description]
|
161
163
|
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]
|
162
164
|
stringByReplacingOccurrencesOfString:@" " withString:@""];
|
163
165
|
NSLog(@"My token is: [%@]", tokenAsString);
|
164
166
|
[self sendToken:tokenAsString];
|
165
|
-
}
|
166
167
|
|
167
168
|
}
|
168
169
|
|
@@ -211,6 +212,17 @@ open your web browser and access http://localhost:4567/ (localhost should be cha
|
|
211
212
|
|
212
213
|

|
213
214
|
|
215
|
+
4. How to run ass in background?
|
216
|
+
-------
|
217
|
+
|
218
|
+
$ nohup ass
|
219
|
+
|
220
|
+
control + z to return to shell prompt
|
221
|
+
|
222
|
+
$ bg
|
223
|
+
|
224
|
+
now ass is running as a background service .
|
225
|
+
|
214
226
|
Contributing to ass
|
215
227
|
=======
|
216
228
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.24
|
data/ass.yml
CHANGED
data/lib/ass.rb
CHANGED
@@ -1,406 +1,4 @@
|
|
1
1
|
#encoding: utf-8
|
2
2
|
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require 'sequel'
|
6
|
-
require 'socket'
|
7
|
-
require 'openssl'
|
8
|
-
require 'cgi'
|
9
|
-
require 'rufus/scheduler'
|
10
|
-
require 'eventmachine'
|
11
|
-
require 'sinatra/base'
|
12
|
-
require 'rack/mobile-detect'
|
13
|
-
require 'yaml'
|
14
|
-
require 'uri-handler'
|
15
|
-
require 'net/http'
|
16
|
-
require 'active_support'
|
17
|
-
require 'json'
|
18
|
-
require 'digest/sha2'
|
19
|
-
require 'will_paginate'
|
20
|
-
require 'will_paginate/sequel' # or data_mapper/sequel
|
21
|
-
require 'uri'
|
22
|
-
require 'sinatra/reloader' if development?
|
23
|
-
require 'sinatra/synchrony'
|
24
|
-
|
25
|
-
############################################################
|
26
|
-
## Initilization Setup
|
27
|
-
############################################################
|
28
|
-
LIBDIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
29
|
-
ROOTDIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
30
|
-
unless $LOAD_PATH.include?(LIBDIR)
|
31
|
-
$LOAD_PATH << LIBDIR
|
32
|
-
end
|
33
|
-
|
34
|
-
unless File.exist?("#{Dir.pwd}/ass.yml") then
|
35
|
-
puts 'create config file: ass.yml'
|
36
|
-
system "cp #{ROOTDIR}/ass.yml #{Dir.pwd}/ass.yml"
|
37
|
-
end
|
38
|
-
|
39
|
-
unless File.exist?("#{Dir.pwd}/cron") then
|
40
|
-
puts "create a demo 'cron' script"
|
41
|
-
system "cp #{ROOTDIR}/cron #{Dir.pwd}/cron"
|
42
|
-
end
|
43
|
-
|
44
|
-
############################################################
|
45
|
-
## Configuration Setup
|
46
|
-
############################################################
|
47
|
-
env = ENV['SINATRA_ENV'] || "development"
|
48
|
-
config = YAML.load_file("#{Dir.pwd}/ass.yml")
|
49
|
-
$timer = "#{config['timer']}".to_i
|
50
|
-
$cron = config['cron'] || 'cron'
|
51
|
-
$port = "#{config['port']}".to_i || 4567
|
52
|
-
$mode = config['mode'] || env
|
53
|
-
$VERSION = File.open("#{ROOTDIR}/VERSION", "rb").read
|
54
|
-
$apps = config['apps'] || []
|
55
|
-
$log = config['log'] || 'off'
|
56
|
-
$user = config['user'] || 'admin'
|
57
|
-
$pass = config['pass'] || 'pass'
|
58
|
-
$flood = "#{config['flood']}".to_i || 1 # default 1 minute
|
59
|
-
|
60
|
-
$client_ip = '127.0.0.1'
|
61
|
-
$last_access = 0
|
62
|
-
|
63
|
-
############################################################
|
64
|
-
## Certificate Key Setup
|
65
|
-
############################################################
|
66
|
-
|
67
|
-
$certkey = {}
|
68
|
-
|
69
|
-
def check_cert
|
70
|
-
$apps.each { |app|
|
71
|
-
unless File.exist?("#{Dir.pwd}/#{app}_#{$mode}.pem") then
|
72
|
-
puts "Please provide #{app}_#{$mode}.pem under '#{Dir.pwd}/' directory"
|
73
|
-
return false;
|
74
|
-
else
|
75
|
-
puts "'#{app}'s #{$mode} PEM: (#{app}_#{$mode}.pem)"
|
76
|
-
certfile = File.read("#{Dir.pwd}/#{app}_#{$mode}.pem")
|
77
|
-
openSSLContext = OpenSSL::SSL::SSLContext.new
|
78
|
-
openSSLContext.cert = OpenSSL::X509::Certificate.new(certfile)
|
79
|
-
openSSLContext.key = OpenSSL::PKey::RSA.new(certfile)
|
80
|
-
$certkey["#{app}"] = openSSLContext
|
81
|
-
end
|
82
|
-
}
|
83
|
-
return true
|
84
|
-
end
|
85
|
-
|
86
|
-
unless check_cert then
|
87
|
-
html = <<-END
|
88
|
-
1: please provide certificate key pem file under current directory, name should be: appid_development.pem for development and appid_production.pem for production
|
89
|
-
2: edit your ass.yml under current directory
|
90
|
-
3: run ass
|
91
|
-
4: iOS Client: in AppDelegate file, didRegisterForRemoteNotificationsWithDeviceToken method should access url below:
|
92
|
-
END
|
93
|
-
$apps.each { |app|
|
94
|
-
html << "'#{app}'s registration url: http://serverIP:#{$port}/v1/apps/#{app}/DeviceToken"
|
95
|
-
}
|
96
|
-
html << "5: Server: cron should access 'curl http://localhost:#{$port}/v1/app/push/{messages}/{pid}' to send push message"
|
97
|
-
puts html
|
98
|
-
exit
|
99
|
-
else
|
100
|
-
html = <<-END
|
101
|
-
#{'*'*80}
|
102
|
-
Apple Service Server(#{$VERSION}) is Running ...
|
103
|
-
Push Notification Service: Enabled
|
104
|
-
Mode: #{$mode}
|
105
|
-
Port: #{$port}
|
106
|
-
END
|
107
|
-
html << "#{'*'*80}"
|
108
|
-
html << "Cron Job: '#{Dir.pwd}/#{$cron}' script is running every #{$timer} #{($timer == 1) ? 'minute' : 'minutes'} " unless "#{$timer}".to_i == 0
|
109
|
-
html << "\n"
|
110
|
-
html << "access http://localhost:#{$port}/ for more information"
|
111
|
-
html << "\n"
|
112
|
-
html << "#{'*'*80}"
|
113
|
-
puts html
|
114
|
-
end
|
115
|
-
|
116
|
-
############################################################
|
117
|
-
## Sequel Database Setup
|
118
|
-
############################################################
|
119
|
-
|
120
|
-
unless File.exist?("#{Dir.pwd}/ass-#{$mode}.db") then
|
121
|
-
$DB = Sequel.connect("sqlite://#{Dir.pwd}/ass-#{$mode}.db")
|
122
|
-
$DB.create_table :tokens do
|
123
|
-
primary_key :id
|
124
|
-
String :app, :unique => false, :null => false
|
125
|
-
String :token, :unique => false, :null => false, :size => 100
|
126
|
-
Time :created_at
|
127
|
-
index [:app, :token]
|
128
|
-
end
|
129
|
-
$DB.create_table :pushes do
|
130
|
-
primary_key :id
|
131
|
-
String :pid, :unique => false, :null => false, :size => 100
|
132
|
-
String :app, :unique => false, :null => false, :size => 30
|
133
|
-
String :message, :unique => false, :null => false, :size => 107
|
134
|
-
Time :created_at
|
135
|
-
index [:pid, :app, :message]
|
136
|
-
end
|
137
|
-
else
|
138
|
-
$DB = Sequel.connect("sqlite://#{Dir.pwd}/ass-#{$mode}.db")
|
139
|
-
end
|
140
|
-
|
141
|
-
WillPaginate.per_page = 10
|
142
|
-
|
143
|
-
class Token < Sequel::Model
|
144
|
-
Sequel.extension :pagination
|
145
|
-
end
|
146
|
-
|
147
|
-
class Push < Sequel::Model
|
148
|
-
Sequel.extension :pagination
|
149
|
-
end
|
150
|
-
|
151
|
-
############################################################
|
152
|
-
## Timer Job Setup
|
153
|
-
############################################################
|
154
|
-
scheduler = Rufus::Scheduler.start_new
|
155
|
-
|
156
|
-
unless $timer == 0 then
|
157
|
-
scheduler.every "#{$timer}m" do
|
158
|
-
puts "running job: '#{Dir.pwd}/#{$cron}' every #{$timer} #{($timer == 1) ? 'minute' : 'minutes'}"
|
159
|
-
system "./#{$cron}"
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
############################################################
|
164
|
-
## Apple Service Server based on Sinatra
|
165
|
-
############################################################
|
166
|
-
|
167
|
-
class App < Sinatra::Base
|
168
|
-
|
169
|
-
register Sinatra::Synchrony
|
170
|
-
|
171
|
-
use Rack::MobileDetect
|
172
|
-
|
173
|
-
set :root, File.expand_path('../../', __FILE__)
|
174
|
-
set :port, "#{$port}".to_i
|
175
|
-
set :public_folder, File.dirname(__FILE__) + '/../public'
|
176
|
-
set :views, File.dirname(__FILE__) + '/../views'
|
177
|
-
|
178
|
-
helpers do
|
179
|
-
|
180
|
-
def checkFlood?(req)
|
181
|
-
if $client_ip != "#{req.ip}" then
|
182
|
-
$client_ip = "#{req.ip}"
|
183
|
-
return false
|
184
|
-
else
|
185
|
-
if $last_access == 0 then
|
186
|
-
return false
|
187
|
-
else
|
188
|
-
return isFlood?
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def isFlood?
|
194
|
-
result = (Time.now - $last_access) < $flood * 60
|
195
|
-
$last_access = Time.now
|
196
|
-
return result
|
197
|
-
end
|
198
|
-
|
199
|
-
def iOS?
|
200
|
-
result = case request.env['X_MOBILE_DEVICE']
|
201
|
-
when /iPhone|iPod|iPad/ then
|
202
|
-
true
|
203
|
-
else false
|
204
|
-
end
|
205
|
-
return result
|
206
|
-
end
|
207
|
-
|
208
|
-
def authorized?
|
209
|
-
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
210
|
-
@auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == ["#{$user}", "#{$pass}"]
|
211
|
-
end
|
212
|
-
|
213
|
-
def protected!
|
214
|
-
unless authorized?
|
215
|
-
response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
|
216
|
-
throw(:halt, [401, "Oops... we need your login name & password\n"])
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
configure :production, :development do
|
222
|
-
if "#{$log}".strip == 'on' then
|
223
|
-
enable :logging
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
if "#{$mode}".strip == 'development' then
|
228
|
-
set :show_exceptions, true
|
229
|
-
set :dump_errors, true
|
230
|
-
else
|
231
|
-
set :show_exceptions, false
|
232
|
-
set :dump_errors, false
|
233
|
-
end
|
234
|
-
|
235
|
-
get '/' do
|
236
|
-
erb :index
|
237
|
-
end
|
238
|
-
|
239
|
-
get '/about' do
|
240
|
-
erb :about
|
241
|
-
end
|
242
|
-
|
243
|
-
not_found do
|
244
|
-
erb :not_found
|
245
|
-
end
|
246
|
-
|
247
|
-
error do
|
248
|
-
@error = "";
|
249
|
-
@error = params['captures'].first.inspect if "#{$mode}".strip == 'development'
|
250
|
-
end
|
251
|
-
|
252
|
-
post '/v1/send' do
|
253
|
-
app = params[:app]
|
254
|
-
message = CGI::escape(params[:message] || "")
|
255
|
-
pid = "#{Time.now.to_i}"
|
256
|
-
# begin
|
257
|
-
# url = URI.parse("http://localhost:#{$port}/v1/apps/#{app}/push")
|
258
|
-
# post_args1 = { :alert => "#{message}".encode('UTF-8'), :pid => "#{pid}" }
|
259
|
-
# Net::HTTP.post_form(url, post_args1)
|
260
|
-
# rescue =>err
|
261
|
-
# puts "#{err.class} ##{err}"
|
262
|
-
# end
|
263
|
-
system "curl http://localhost:#{$port}/v1/apps/#{app}/push/#{message}/#{pid}"
|
264
|
-
redirect '/v1/admin/push' if (params[:app] and params[:message])
|
265
|
-
end
|
266
|
-
|
267
|
-
get "/v1/admin/:db" do
|
268
|
-
protected!
|
269
|
-
db = params[:db] || 'token'
|
270
|
-
page = 1
|
271
|
-
page = params[:page].to_i if params[:page]
|
272
|
-
if (db == 'token') then
|
273
|
-
@o = []
|
274
|
-
$apps.each_with_index { |app, index|
|
275
|
-
@o << Token.where(:app => app).order(:id).reverse.paginate(page, 20)
|
276
|
-
}
|
277
|
-
erb :token
|
278
|
-
elsif (db == 'push') then
|
279
|
-
@p = []
|
280
|
-
$apps.each_with_index { |app, index|
|
281
|
-
@p << Push.where(:app => app).order(:id).reverse.paginate(page, 20)
|
282
|
-
}
|
283
|
-
erb :push
|
284
|
-
else
|
285
|
-
erb :not_found
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
$apps.each { |app|
|
290
|
-
|
291
|
-
## register token api
|
292
|
-
get "/v1/apps/#{app}/:token" do
|
293
|
-
if (("#{params[:token]}".length == 64) and iOS? and checkFlood?(request) ) then
|
294
|
-
puts "[#{params[:token]}] was added to '#{app}'" if "#{$mode}".strip == 'development'
|
295
|
-
o = Token.first(:app => app, :token => params[:token])
|
296
|
-
unless o
|
297
|
-
Token.insert(
|
298
|
-
:app => app,
|
299
|
-
:token => params[:token],
|
300
|
-
:created_at => Time.now
|
301
|
-
)
|
302
|
-
end
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
## http POST method push api
|
307
|
-
post "/v1/apps/#{app}/push" do
|
308
|
-
protected! unless request.host == 'localhost'
|
309
|
-
message = CGI::unescape(params[:alert] || "")[0..107]
|
310
|
-
badge = 1
|
311
|
-
puts "params[:badge] = [#{params[:badge]}]"
|
312
|
-
badge = params[:badge].to_i if params[:badge] and params[:badge] != ''
|
313
|
-
sound = CGI::unescape(params[:sound] || "")
|
314
|
-
extra = CGI::unescape(params[:extra] || "")
|
315
|
-
|
316
|
-
puts "#{badge} : #{message} extra: #{extra}" if "#{$mode}".strip == 'development'
|
317
|
-
pid = params[:pid]
|
318
|
-
|
319
|
-
puts "'#{message}' was sent to (#{app}) with pid: [#{pid}], badge:#{badge} , sound: #{sound}, extra:#{extra}" if "#{$mode}".strip == 'development'
|
320
|
-
|
321
|
-
@tokens = Token.where(:app => "#{app}")
|
322
|
-
@exist = Push.first(:pid => "#{pid}", :app => "#{app}")
|
323
|
-
|
324
|
-
unless @exist
|
325
|
-
|
326
|
-
Push.insert(:pid => pid, :message => message, :created_at => Time.now, :app => "#{app}" )
|
327
|
-
|
328
|
-
openSSLContext = $certkey["#{app}"]
|
329
|
-
# Connect to port 2195 on the server.
|
330
|
-
sock = nil
|
331
|
-
if $mode == 'production' then
|
332
|
-
sock = TCPSocket.new('gateway.push.apple.com', 2195)
|
333
|
-
else
|
334
|
-
sock = TCPSocket.new('gateway.sandbox.push.apple.com', 2195)
|
335
|
-
end
|
336
|
-
# do our SSL handshaking
|
337
|
-
sslSocket = OpenSSL::SSL::SSLSocket.new(sock, openSSLContext)
|
338
|
-
sslSocket.connect
|
339
|
-
|
340
|
-
# write our packet to the stream
|
341
|
-
@tokens.each do |o|
|
342
|
-
tokenText = o[:token]
|
343
|
-
# pack the token to convert the ascii representation back to binary
|
344
|
-
tokenData = [tokenText].pack('H*')
|
345
|
-
# construct the payload
|
346
|
-
po = {:aps => {:alert => "#{message}", :badge => badge, :sound => "#{sound}"}, :extra => "#{extra}"}
|
347
|
-
payload = ActiveSupport::JSON.encode(po)
|
348
|
-
# construct the packet
|
349
|
-
packet = [0, 0, 32, tokenData, 0, payload.length, payload].pack("ccca*cca*")
|
350
|
-
# read our certificate and set up our SSL context
|
351
|
-
sslSocket.write(packet)
|
352
|
-
end
|
353
|
-
# cleanup
|
354
|
-
sslSocket.close
|
355
|
-
sock.close
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
## http GET method push api
|
360
|
-
get "/v1/apps/#{app}/push/:message/:pid" do
|
361
|
-
protected! unless request.host == 'localhost'
|
362
|
-
message = CGI::unescape(params[:message])
|
363
|
-
puts message if "#{$mode}".strip == 'development'
|
364
|
-
pid = params[:pid]
|
365
|
-
|
366
|
-
puts "'#{message}' was sent to (#{app}) with pid: [#{pid}]" if "#{$mode}".strip == 'development'
|
367
|
-
|
368
|
-
@tokens = Token.where(:app => "#{app}")
|
369
|
-
@exist = Push.first(:pid => "#{pid}", :app => "#{app}")
|
370
|
-
|
371
|
-
unless @exist
|
372
|
-
|
373
|
-
Push.insert(:pid => pid, :message => message, :created_at => Time.now, :app => "#{app}" )
|
374
|
-
|
375
|
-
openSSLContext = $certkey["#{app}"]
|
376
|
-
# Connect to port 2195 on the server.
|
377
|
-
sock = nil
|
378
|
-
if $mode == 'production' then
|
379
|
-
sock = TCPSocket.new('gateway.push.apple.com', 2195)
|
380
|
-
else
|
381
|
-
sock = TCPSocket.new('gateway.sandbox.push.apple.com', 2195)
|
382
|
-
end
|
383
|
-
# do our SSL handshaking
|
384
|
-
sslSocket = OpenSSL::SSL::SSLSocket.new(sock, openSSLContext)
|
385
|
-
sslSocket.connect
|
386
|
-
|
387
|
-
# write our packet to the stream
|
388
|
-
@tokens.each do |o|
|
389
|
-
tokenText = o[:token]
|
390
|
-
# pack the token to convert the ascii representation back to binary
|
391
|
-
tokenData = [tokenText].pack('H*')
|
392
|
-
# construct the payload
|
393
|
-
po = {:aps => {:alert => "#{message}", :badge => 1}}
|
394
|
-
payload = ActiveSupport::JSON.encode(po)
|
395
|
-
# construct the packet
|
396
|
-
packet = [0, 0, 32, tokenData, 0, payload.length, payload].pack("ccca*cca*")
|
397
|
-
# read our certificate and set up our SSL context
|
398
|
-
sslSocket.write(packet)
|
399
|
-
end
|
400
|
-
# cleanup
|
401
|
-
sslSocket.close
|
402
|
-
sock.close
|
403
|
-
end
|
404
|
-
end
|
405
|
-
}
|
406
|
-
end
|
3
|
+
require 'ass/conf'
|
4
|
+
require 'ass/app'
|