library.py 11.6 KB
Newer Older
Jean Michel Rouly's avatar
Jean Michel Rouly committed
1
from cgi import escape
Jean Michel Rouly's avatar
Jean Michel Rouly committed
2
import random
3
import time
Jean Michel Rouly's avatar
Jean Michel Rouly committed
4
5
import math
import site
6
7
8
9
import MySQLdb
import Cookie
import cookielib
import hashlib
10
import ldap
Jean Michel Rouly's avatar
Jean Michel Rouly committed
11

12
site.addsitedir('/srv/http/go/wsgi')
13
import goconfig
Jean Michel Rouly's avatar
Jean Michel Rouly committed
14

Jean Michel Rouly's avatar
Jean Michel Rouly committed
15

16
17
# Extract the value of the cookie if one is set.
def get_cookie_value( environ ):
18
  cookie = Cookie.SimpleCookie()
19

20
21
  # if the environment contains a cookie, check it out
  if environ.has_key('HTTP_COOKIE'):
Jean Michel Rouly's avatar
Jean Michel Rouly committed
22
23
    # load the cookie we found
    cookie.load(environ['HTTP_COOKIE']);
24
25
    if cookie.has_key('user'):
      user_hash = cookie['user'].value
Jean Michel Rouly's avatar
Jean Michel Rouly committed
26
      return escape(user_hash)
27
28
29
30
31
32
33
34
35
36
37
38
39

  return None


# Determine if a user is appropriately validated through LDAP.
def user_logged_in( environ ):
  user_hash = get_cookie_value(environ)
  if( user_hash is not None ):
    # see if it's in the database
    mdb,cursor = connect_to_mysql()
    sql = """SELECT count(*) FROM `%s` WHERE `user_hash`=%s;"""
    cursor.execute( sql, (goconfig.sql_usr_table, user_hash) )
    ((num_rows,),) = cursor.fetchall()
40

41
42
    mdb.commit()
    mdb.close()
43

44
    return num_rows > 0
45

Jean Michel Rouly's avatar
Jean Michel Rouly committed
46
  return False
Jean Michel Rouly's avatar
Jean Michel Rouly committed
47
48


49
def get_username( environ ):
50
51
52
53
54
55
56
57
58
59
60
61
  user_hash = get_cookie_value( environ )
  username = None
  if( user_hash is not None ):
    mdb,cursor = connect_to_mysql()
    try:
      sql = """SELECT `user` FROM `%s` WHERE `user_hash`=%s;"""
      cursor.execute( sql, (goconfig.sql_usr_table, user_hash ) )
      ((username,),) = cursor.fetchall()
      mdb.commit()
    finally:
      mdb.close()
  return username
62
63


64
65
66
67
68
69
70
71
72
73
74
75
76
def user_approved( username ):
  mdb,cursor = connect_to_mysql()
  sql = """SELECT `approved` FROM `%s` WHERE `user`=%s;"""
  cursor.execute( sql, (goconfig.sql_registration_table, username) )
  ((approved,),) = cursor.fetchall()
  mdb.commit()
  mdb.close()
  if approved == 1:
    return True
  else:
    return False


77
78
79
# Determine if the user has posting permissions via the registration
# datbase.
def user_registered( username ):
80
81
82
83
  mdb,cursor = connect_to_mysql()
  sql = """SELECT count(*) FROM `%s` WHERE `user`=%s;"""
  cursor.execute( sql, (goconfig.sql_registration_table, username) )
  ((num_rows,),) = cursor.fetchall()
84

85
86
  mdb.commit()
  mdb.close()
87

88
  return num_rows > 0
89
90


91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# Register a user --- that is, enter them in the registered database with a 
# false (default=0) approval flag.
def register_user( user, name, desc ):
  mdb,cursor = connect_to_mysql()
  output = False
  try:
    sql = """INSERT INTO `%s`(`user`, `name`, `comment`) VALUES (%s, %s, %s)"""
    cursor.execute( sql, (goconfig.sql_registration_table, user, name, desc) )
    output = True
  except MySQLdb.IntegrityError:
    pass
  mdb.commit()
  mdb.close()
  return output


107
108
109
# Log in a user by placing a cookie on their machine and entering
# the related hash in a SQL database.
def generate_cookie( user ):
110
111
112
113
114
115
  # generate a random 32-character salt
  ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  chars=[]
  for i in range(32):
    chars.append( random.choice(ALPHABET) )
  salt = "".join(chars)
116

117
118
  # generate a randomized hash for this user
  hashed_value = hashlib.sha512( user + salt ).hexdigest()
119
120
121
122
123
124
125
  cookie = Cookie.SimpleCookie()
  cookie["user"] = hashed_value
  cookie["user"]["expires"] = ""
  cookie["user"]["path"] = "/"
  return cookie


126
127
128
129
130
131
132
133
134
# Generate an expired cookie in order to remove any preexisting cookie.
def eat_cookie():
  cookie = Cookie.SimpleCookie()
  cookie["user"] = "goodbye"
  cookie["user"]["expires"] = "Thu, 01 Jan 1970 00:00:00 GMT"
  cookie["user"]["path"] = "/"
  return cookie


135
# Register the user in the table of active users.
136
def activate_user( hash_value, user ):
137
  mdb,cursor = connect_to_mysql()
138
139
140
141
  sql = """INSERT INTO `%s` (`user_hash`,`user`) VALUES (%s,%s);"""
  cursor.execute( sql, (goconfig.sql_usr_table, hash_value, user) )
  mdb.commit()
  mdb.close()
142
143
144
145
146


# Unregister the user in the table of active users.
def deactivate_user( hash_value ):
  mdb, cursor = connect_to_mysql()
Jean Michel Rouly's avatar
Jean Michel Rouly committed
147
148
  sql = """DELETE FROM `%s` WHERE `user_hash`=%s;"""
  cursor.execute( sql, (goconfig.sql_usr_table, hash_value) )
149
150
151
152
153
154
155
156
157
158
159
160
161
  mdb.commit()
  mdb.close()


# Connect to a mySQL database and return a pointer to that database.
def connect_to_mysql():
  # Connect to and choose the database.
  mdb = MySQLdb.connect(
    goconfig.sql_domain,
    goconfig.sql_usr,
    goconfig.sql_pasw,
    goconfig.sql_db )
  cursor = mdb.cursor()
162

163
164
165
166
  # If we need to create the table, then construct it.
  # REGISTERED USER TABLE
  sql = """CREATE TABLE IF NOT EXISTS `'%s'`(
  user VARCHAR(50) CHARACTER SET 'utf8' NOT NULL,
167
  PRIMARY KEY(user),
168
169
170
  name VARCHAR(100) CHARACTER SET 'utf8' NOT NULL,
  comment VARCHAR(500) CHARACTER SET 'utf8' NOT NULL,
  approved INT(1) NOT NULL DEFAULT '0'
171
172
173
174
  )
  ENGINE = InnoDB
  DEFAULT CHARACTER SET = latin1;""" % ( goconfig.sql_registration_table )
  cursor.execute( sql )
175

176
177
  # ACTIVE USER TABLE
  sql = """CREATE TABLE IF NOT EXISTS `'%s'`(
178
179
  id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  PRIMARY KEY(id),
180
181
182
183
184
185
186
187
188
189
190
191
192
  user_hash VARCHAR(500) CHARACTER SET 'utf8' NOT NULL,
  user VARCHAR(50) CHARACTER SET 'utf8' NOT NULL,
  CONSTRAINT `fk_active_user` FOREIGN KEY (`user`)
    REFERENCES `%s`.`'%s'`(`user`)
    ON DELETE CASCADE ON UPDATE CASCADE
  )
  ENGINE = InnoDB
  DEFAULT CHARACTER SET = latin1;""" % (
    goconfig.sql_usr_table,
    goconfig.sql_db,
    goconfig.sql_registration_table
  )
  cursor.execute( sql )
193

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
  sql = """CREATE TABLE IF NOT EXISTS `'%s'`(
  id INT NOT NULL AUTO_INCREMENT, 
  PRIMARY KEY(id),
  longurl VARCHAR(1000) CHARACTER SET 'utf8' NOT NULL,
  shorturl VARCHAR(100) CHARACTER SET 'utf8' NOT NULL,
  expiration INT(50) UNSIGNED NOT NULL,
  user VARCHAR(50) CHARACTER SET 'utf8' NOT NULL,
  clicks INT(10) UNSIGNED NOT NULL,
  CONSTRAINT `fk_url_user` FOREIGN KEY (`user`)
    REFERENCES `%s`.`'%s'`(`user`)
    ON DELETE CASCADE ON UPDATE CASCADE
  )
  ENGINE = InnoDB
  DEFAULT CHARACTER SET = latin1;""" % ( 
    goconfig.sql_url_table,
    goconfig.sql_db,
    goconfig.sql_registration_table
  )
  cursor.execute( sql )
213

214
215
216
  return mdb, cursor


Jean Michel Rouly's avatar
Jean Michel Rouly committed
217
218
# Parse post data submitted to this function. That is, split it up as a
# dictionary and return that readable dictionary.
219
def parse_data( datastring ):
Jean Michel Rouly's avatar
Jean Michel Rouly committed
220
221
  delimiter = "&"
  subdelimiter = "="
222
  data = datastring.read()
Jean Michel Rouly's avatar
Jean Michel Rouly committed
223
224
225
226
  if len( data ) > 0:
    # create a dictionary as {field:val, field:val, ... }
    data = dict( item.split(subdelimiter) for item in data.split( delimiter ) )
    return data
227

Jean Michel Rouly's avatar
Jean Michel Rouly committed
228
229
230
231
232
  return None


# Generate a random short url from a list of possible characters
# and the minimum allowed length.
233
def generate_short_url( long_url ):
Jean Michel Rouly's avatar
Jean Michel Rouly committed
234
235
  decimal = 10
  encoding = 62
236

237
238
239
  # determine the range of possible values (set by goconfig.min_url_len)
  min_val = encoding ** (goconfig.min_url_len - 1)
  max_val = (encoding ** goconfig.min_url_len) - 1
240

Jean Michel Rouly's avatar
Jean Michel Rouly committed
241
242
  # generate the short url (some val between min and max)
  value = random.randint( min_val, max_val )
243

Jean Michel Rouly's avatar
Jean Michel Rouly committed
244
245
  # Encode the short url value in the most appropriate base.
  short = []
246

Jean Michel Rouly's avatar
Jean Michel Rouly committed
247
248
  # define the list of possible characters
  charlist = list("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
249

Jean Michel Rouly's avatar
Jean Michel Rouly committed
250
251
252
  while value > 0:
    short.append(charlist[ int(value % encoding) ])
    value = math.floor( value / encoding )
253

Jean Michel Rouly's avatar
Jean Michel Rouly committed
254
255
256
257
258
  return ''.join( short )


# This function should return true if the specified short_url
# already exists in the mySQL database. This prevents overlapping.
259
260
def short_url_exists( short_url ):
  mdb, cursor = connect_to_mysql()
Jean Michel Rouly's avatar
Jean Michel Rouly committed
261
262
  sql = """SELECT * FROM `%s` WHERE `shorturl` = %s;"""
  output = cursor.execute( sql, (goconfig.sql_url_table, short_url) )
263
264
  mdb.commit()
  mdb.close()
Jean Michel Rouly's avatar
Jean Michel Rouly committed
265
  return True if output > 0 else False
Jean Michel Rouly's avatar
Jean Michel Rouly committed
266
267


268
# Inserts a short-url, long-url pairing into the database.
269
270
def register_url( longurl, shorturl, expiration, environ ):
  username = get_username( environ )
271
  mdb, cursor = connect_to_mysql()
272
273
274
  sql = """INSERT INTO `%s`(`id`, `longurl`, `shorturl`, `expiration`, `user`, `clicks`)
  VALUES (NULL, %s, %s, %s, %s, '0')"""
  cursor.execute( sql, (goconfig.sql_url_table, longurl, shorturl, expiration, username) )
275
276
  mdb.commit()
  mdb.close()
277
278


279
280
281
282
283
284
285
286
287
# Deletes a URL from the registered URL table.
def delete_url( url_id ):
  mdb, cursor = connect_to_mysql()
  sql = """DELETE FROM `%s` WHERE `id`=%s;"""
  cursor.execute( sql, (goconfig.sql_url_table, url_id) )
  mdb.commit()
  mdb.close()


288
289
290
291
# Removes any expired urls in the url table.
def remove_expired_urls():
  mdb, cursor = connect_to_mysql()
  today = int(time.time())
292
  sql = """DELETE FROM `%s` WHERE `expiration` > 0 AND `expiration` < %s;"""
293
294
295
  cursor.execute( sql, (goconfig.sql_url_table, today) )
  mdb.commit()
  mdb.close()
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319

def get_redirect_target( short_url ):
  mdb,cursor = connect_to_mysql()
  sql = """SELECT * FROM `%s` WHERE `shorturl` = %s;"""
  output = cursor.execute( sql, (goconfig.sql_url_table, short_url) )

  selection = cursor.fetchall()
  target = None

  # If at least one row has been found, then grab its long_url.
  # If no rows are found, though, then don't do anything more!
  if len(selection) > 0:
    row = selection[0]  # we are only interested in the first result
    target = row[1]     # this is the index of the longurl field
    uid = row[0]        # this is the index of the ID field

    sql = """UPDATE `%s` SET `clicks`=`clicks`+1 WHERE `id`=%s;"""
    cursor.execute( sql, (goconfig.sql_url_table, uid) )

  # Close the connection to the mySQL database.
  mdb.commit()
  mdb.close()

  return target
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343


def ldap_authenticate( usr, psw ):
  bind = 'uid='+usr+',ou=people,o=gmu.edu'

  if( len(usr) > 0 and len(psw) > 0):

    # Try to talk with the LDAP server.
    ldap.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
    ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)

    try:
      ld = ldap.initialize( goconfig.ldap_domain )
      result = ld.simple_bind_s( bind, psw )
      if result is not None:
        return True
    except ldap.INVALID_CREDENTIALS:
      pass
    except ldap.INAPPROPRIATE_AUTH:
      pass
    except ldap.NO_SUCH_OBJECT:
      pass

  return False
344
345
346


def get_top( logged_in=False ):
347
  f = open(goconfig.doc_root + "/static/top.part", "r")
348
349
350
  top = f.read()
  f.close()

351
352
353
354
355
356
  myaccount = """| [ <a href="/account">My Account</a> ]"""
  if logged_in:
    top = top.replace( "%{myaccount}%", myaccount )
  else:
    top = top.replace( "%{myaccount}%", "" )

357
358
359
360
361
362
363
364
365
366
  mylinks = """| [ <a href="/mylinks">My Links</a> ]"""
  if logged_in:
    top = top.replace( "%{mylinks}%", mylinks )
  else:
    top = top.replace( "%{mylinks}%", "" )

  return top


def get_bottom( logged_in=False ):
367
  f = open(goconfig.doc_root + "/static/bottom.part", "r")
368
369
370
  bottom = f.read()
  f.close()

371
372
373
374
375
376
  logout = """<a href="/logout">Log Out</a>"""
  if logged_in:
    bottom = bottom.replace( "%{logout}%", logout )
  else:
    bottom = bottom.replace( "%{logout}%", "" )

377
  return bottom
378
379
380
381
382
383
384
385
386
387


def get_links( username ):
  mdb,cursor = connect_to_mysql()
  sql = """SELECT * FROM `%s` WHERE `user`=%s;"""
  cursor.execute( sql, (goconfig.sql_url_table, username) )
  result = cursor.fetchall()
  mdb.commit()
  mdb.close()
  return result
388

389
def piwik_track( environ, page ):
390
391
392
393
  from piwikapi.tracking import PiwikTracker
  from piwikapi.tests.request import FakeRequest

  headers = {
394
395
396
397
398
399
400
    'HTTP_USER_AGENT': environ.get('HTTP_USER_AGENT'),
    'REMOTE_ADDR': environ.get('REMOTE_ADDR'),
    'HTTP_REFERER': environ.get('HTTP_REFERER'),
    'HTTP_ACCEPT_LANGUAGE': environ.get('HTTP_ACCEPT_LANGUAGE'),
    'SERVER_NAME': environ.get('SERVER_NAME'),
    'PATH_INFO': environ.get('PATH_INFO'),
    'QUERY_STRING': environ.get('QUERY_STRING'),
401
402
403
404
405
406
407
408
409
410
411
    'HTTPS': False,
  }

  request = FakeRequest(headers)
  piwiktracker = PiwikTracker(goconfig.piwik_site_id, request)
  piwiktracker.set_api_url(goconfig.piwik_tracking_api_url)

  piwiktracker.set_ip(headers['REMOTE_ADDR'])
  #piwiktracker.set_token_auth(PIWIK_TOKEN_AUTH)

  # submit tracking entry
412
  piwiktracker.do_track_page_view( str(page) )