RAPP Platform  v0.6.0
RAPP Platform is a collection of ROS nodes and back-end processes that aim to deliver ready-to-use generic services to robots
 All Classes Namespaces Files Functions Variables Macros
application_authentication_node.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 #Copyright 2015 RAPP
5 
6 #Licensed under the Apache License, Version 2.0 (the "License");
7 #you may not use this file except in compliance with the License.
8 #You may obtain a copy of the License at
9 
10  #http://www.apache.org/licenses/LICENSE-2.0
11 
12 #Unless required by applicable law or agreed to in writing, software
13 #distributed under the License is distributed on an "AS IS" BASIS,
14 #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 #See the License for the specific language governing permissions and
16 #limitations under the License.
17 
18 # Authors: Aris Thallas
19 # contact: aris.thallas@{iti.gr, gmail.com}
20 
21 import re
22 import random
23 import string
24 import bcrypt
25 import base64
26 from passlib.hash import sha256_crypt
27 
28 import rospy
29 
30 from database_handler import DatabaseHandler
31 
32 from rapp_utilities import RappUtilities
33 from rapp_exceptions import RappError
34 
35 from rapp_platform_ros_communications.srv import (
36  AddNewUserFromPlatformSrv,
37  AddNewUserFromPlatformSrvResponse,
38  AddNewUserFromStoreSrv,
39  AddNewUserFromStoreSrvResponse,
40  UserTokenAuthenticationSrv,
41  UserTokenAuthenticationSrvResponse,
42  UserLoginSrv,
43  UserLoginSrvResponse
44  )
45 
46 
47 ## @class ApplicationAuthenticationManager
48 # @brief Provides user management features
50 
51  ## @brief ROS Service initializations
52  def __init__(self):
53 
54  ## Handles the database queries
55  #
56  # (see database_handler.DatabaseHandler)
57  self._db_handler = DatabaseHandler()
58 
59  # Create new user using platform credentials
60  rapp_add_new_user_from_platform_topic = \
61  rospy.get_param("rapp_add_new_user_from_platform_topic")
62  if not rapp_add_new_user_from_platform_topic:
63  msg = "Add new user from platform topic does not exist"
64  RappUtilities.rapp_print(msg, 'ERROR')
65  add_new_user_from_platform_srv = \
66  rospy.Service(rapp_add_new_user_from_platform_topic,
67  AddNewUserFromPlatformSrv,
69 
70  # Create new user using store credentials
71  rapp_add_new_user_from_store_topic = \
72  rospy.get_param("rapp_add_new_user_from_store_topic")
73  if not rapp_add_new_user_from_store_topic:
74  msg = "Add new user from store topic does not exist"
75  RappUtilities.rapp_print(msg, 'ERROR')
76  add_new_user_srv = rospy.Service(rapp_add_new_user_from_store_topic,
77  AddNewUserFromStoreSrv,
79 
80  # Token authentication service
81  authenticate_token_topic = \
82  rospy.get_param("rapp_authenticate_token_topic")
83  if not authenticate_token_topic:
84  rospy.logerr("Application authentication: " +
85  "Token authentication topic does not exist")
86 
87  authenticate_token_service = \
88  rospy.Service(authenticate_token_topic,
89  UserTokenAuthenticationSrv,
91 
92  # Login using store credentials
93  login_from_store_topic = \
94  rospy.get_param("rapp_login_user_from_store")
95  if not login_from_store_topic:
96  msg = "Login user from store topic does not exist"
97  RappUtilities.rapp_print(msg, 'ERROR')
98  login_user_from_store_srv = \
99  rospy.Service(login_from_store_topic,
100  UserLoginSrv,
102 
103  # Login
104  login_topic = \
105  rospy.get_param("rapp_login_user")
106  if not login_topic:
107  msg = "Login user topic does not exist"
108  RappUtilities.rapp_print(msg, 'ERROR')
109 
110  login_user_srv = \
111  rospy.Service(login_topic,
112  UserLoginSrv,
113  self.login_callback)
114 
115  ## @brief Add new platform user using platform credentials
116  #
117  # @param req [rapp_platform_ros_communications::ApplicationAuthentication::AddNewUserFromPlatformSrvRequest] The service request
118  #
119  # @return res [rapp_platform_ros_communications::ApplicationAuthentication::AddNewUserFromPlatformSrvResponse] The service response
121  res = AddNewUserFromPlatformSrvResponse()
122 
123  try:
125  req.creator_username, req.creator_password)
126 
127  self._db_handler.validate_user_role(req.creator_username)
128 
129  self._validate_username_format(req.new_user_username)
130  except RappError as e:
131  res.error = e.value
132  return res
133 
134  try:
135  unique_username = \
136  self._verify_new_username_uniqueness(req.new_user_username)
137  except RappError as e:
138  res.error = e.value
139  return res
140  else:
141  if unique_username != '':
142  res.error = 'Username exists'
143  res.suggested_username = unique_username
144  return res
145 
146  try:
147  self._add_new_user_to_db(
148  req.new_user_username,
149  req.new_user_password,
150  req.creator_username,
151  req.language
152  )
153  except RappError as e:
154  res.error = e.value
155  return res
156 
157  ## @brief Add new platform user using rapp_store credentials
158  #
159  # @param req [rapp_platform_ros_communications::ApplicationAuthentication::AddNewUserFromStoreSrvRequest] The service request
160  #
161  # @return res [rapp_platform_ros_communications::ApplicationAuthentication::AddNewUserFromStoreSrvResponse] The service response
163  res = AddNewUserFromStoreSrvResponse()
164  res.error = ''
165 
166  # Verify that username -> alphanumeric + dash + underscore
167  try:
168  self._validate_username_format(req.username)
169  except RappError as e:
170  res.error = e.value
171  return res
172 
173  if not self._db_handler.verify_store_device_token(req.device_token):
174  res.error = 'Invalid user'
175  return res
176 
177  # Verify that username is unique, i.e. does not exist
178  try:
179  unique_username = \
180  self._verify_new_username_uniqueness(req.username)
181  except RappError as e:
182  res.error = e.value
183  return res
184  else:
185  if unique_username != '':
186  res.error = 'Username exists'
187  res.suggested_username = unique_username
188  return res
189 
190  # Add new user to the database
191  try:
192  self._add_new_user_to_db(
193  req.username,
194  req.password,
195  'rapp_store',
196  req.language
197  )
198  except RappError as e:
199  res.error = e.value
200  return res
201 
202  ## @brief Login existing user using platform device token
203  #
204  # @param req [rapp_platform_ros_communications::ApplicationAuthentication::UserLoginSrvRequest] The service request
205  #
206  # @return res [rapp_platform_ros_communications::ApplicationAuthentication::UserLoginSrvResponse] The service response
207  def login_callback(self, req):
208  res = UserLoginSrvResponse()
209 
210  try:
211  self._verify_user_credentials(req.username, req.password)
212  except RappError as e:
213  res.error = e.value
214  return res
215 
216  if not self._db_handler.verify_platform_device_token(req.device_token):
217  res.error = 'Invalid user'
218  return res
219 
220  if self._db_handler.verify_active_robot_session(
221  req.username, req.device_token):
222  res.error = 'Session already active'
223  return res
224 
225  rand_str = \
226  ''.join(random.SystemRandom().choice(string.ascii_letters +
227  string.digits + string.punctuation) for _ in range(64))
228  hash_str = sha256_crypt.encrypt(rand_str)
229  index = hash_str.find('$', 3)
230  hash_str = hash_str[index+1:]
231  new_token = base64.b64encode(hash_str)
232 
233  try:
234  self._db_handler.write_new_application_token(
235  req.username, req.device_token, new_token)
236  except RappError as e:
237  res.error = 'Wrong credentials'
238  else:
239  res.error = ''
240  res.token = new_token
241  return res
242 
243  ## @brief Login existing user using store device token
244  #
245  # @param req [rapp_platform_ros_communications::ApplicationAuthentication::UserLoginSrvRequest] The service request
246  #
247  # @return res [rapp_platform_ros_communications::ApplicationAuthentication::UserLoginSrvResponse] The service response
249  res = UserLoginSrvResponse()
250 
251  try:
252  self._verify_user_credentials(req.username, req.password)
253  except RappError as e:
254  res.error = e.value
255  return res
256 
257  if not self._db_handler.verify_store_device_token(req.device_token):
258  res.error = 'Invalid user'
259  return res
260  else:
261  try:
262  self._db_handler.add_store_token_to_device(req.device_token)
263  except RappError as e:
264  res.error = 'Wrong credentials'
265  return res
266 
267  if self._db_handler.verify_active_robot_session(
268  req.username, req.device_token):
269  res.error = 'Session already active'
270  return res
271 
272  # Generate token
273  rand_str = \
274  ''.join(random.SystemRandom().choice(string.ascii_letters +
275  string.digits + string.punctuation) for _ in range(64))
276  hash_str = sha256_crypt.encrypt(rand_str)
277  index = hash_str.find('$', 3)
278  hash_str = hash_str[index+1:]
279  res.token = base64.b64encode(hash_str)
280 
281  try:
282  self._db_handler.write_new_application_token(
283  req.username, req.device_token, res.token)
284  except RappError as e:
285  res.error = 'Wrong credentials'
286  else:
287  res.error = ''
288  return res
289 
290  ## @brief Authenticate token
291  #
292  # @param req [rapp_platform_ros_communications::ApplicationAuthentication::UserTokenAuthenticationSrvRequest] The service request
293  #
294  # @return res [rapp_platform_ros_communications::ApplicationAuthentication::UserTokenAuthenticationSrvResponse] The service response
296 
297  res = UserTokenAuthenticationSrvResponse()
298  res.error = ''
299  res.username = ''
300  if self._db_handler.verify_active_application_token(req.token):
301  res.username = self._db_handler.get_token_user(req.token)
302  else:
303  res.error = 'Invalid token'
304  return res
305 
306  ## @brief Verify username and password
307  #
308  # @param username [string] The username
309  # @param password [string] Password associated with username
310  #
311  # @exception RappError Username does not exist or password does not match
312  def _verify_user_credentials(self, username, password):
313  passwd = self._db_handler.get_user_password(username)
314  if bcrypt.hashpw(password, passwd) != passwd:
315  raise RappError("Wrong Credentials")
316 
317  ## @brief Verify that new username is unique
318  #
319  # @param username [string] The username
320  #
321  # @return suggestion [string] A suggested username if the provided already exists
322  #
323  # @exception RappError Username exists and can not find a username suggestion
324  def _verify_new_username_uniqueness(self, username):
325  if self._db_handler.username_exists(username):
326  counter = 0
327  while True and counter <= 10:
328  counter += 1
329  suggestion = '_' + \
330  ''.join(random.SystemRandom().choice(string.digits)
331  for _ in range(5))
332  if not self._db_handler.username_exists(
333  username + suggestion):
334  return username + suggestion
335  raise RappError('Could specify a username suggestion')
336  else:
337  return ''
338 
339  ## @brief Verify that new username complies with a set of rules
340  #
341  # Alphanumerics, dashes and underscores are allowed
342  #
343  # @param username [string] The username
344  #
345  # @exception RappError Username violates rules
346  def _validate_username_format(self, username):
347  if not re.match("^[\w\d_-]*$", username) or len(username) < 5 or \
348  len(username) > 15:
349  raise RappError('Invalid username characters')
350 
351  ## @brief Create password hash and store to the databse
352  #
353  # @param username [string] The username
354  # @param password [string] The password
355  def _add_new_user_to_db(self, new_user_username, new_user_password,
356  creator_username, language):
357 
358  password_hash = bcrypt.hashpw(new_user_password, bcrypt.gensalt())
359 
360  self._db_handler.add_new_user(
361  new_user_username, password_hash, creator_username, language)
362 
363 if __name__ == "__main__":
364  rospy.init_node('application_authentication_node')
365  application_authentication_ros_node = ApplicationAuthenticationManager()
366  rospy.spin()