Commit 3e82f58e authored by Mohd Bilal's avatar Mohd Bilal
Browse files

added user authentication

parent 961e5ebc
from utils.helpers import setup_required
from app.client.operations import RequestSender, RequestHandler
from app.models.users import User
from app.models.satellites import Satellite
from app.models.stations import Station
......@@ -38,49 +37,11 @@ class TIMGSNClient:
for user_data in users_data:
user = User(username=os.getenv(user_data.get("username_env")),
password=os.getenv(user_data.get("password_env")))
authenticated = user.authenticate(os.getenv(user_data.get("username_env")),
user.authenticate(os.getenv(user_data.get("username_env")),
os.getenv(user_data.get("password_env")))
if 'satellites' in user_data:
for norad in user_data.get("satellites"):
satellite = Satellite(norad)
try:
satellite.load_from_json()
except Exception as err:
logger.exception(err)
user.add_satellite(satellite)
if 'stations' in user_data:
for station_name in user_data.get("stations"):
station = Station(station_name)
try:
station.load_from_json()
except Exception as err:
logger.exception(err)
user_sender_data = user_data.get("operations")\
.get('requests')\
.get('sender')
user_receiver_data = user_data.get("operations")\
.get('requests')\
.get('receiver')
if not user_sender_data:
user_sender_data = self.settings\
.get('client')\
.get('operations')\
.get('requests')\
.get('sender')
if not user_receiver_data:
user_receiver_data = self.settings\
.get('client')\
.get('operations')\
.get('requests')\
.get('receiver')
request_sender = RequestSender(f'Request Sender: {user.username}',
**user_sender_data)
request_handler = RequestHandler(
f'Request Handler: {user.username}', **user_receiver_data)
user.add_request_sender(request_sender)
user.add_request_handler(request_handler)
logger.debug(f"user is authenticated: {user.is_authenticated}")
user.load_data(user_data)
self.users.append(user)
self._is_setup = True
def _tear_down(self):
......@@ -98,5 +59,4 @@ class TIMGSNClient:
def start(self):
self._set_up()
logger.info("starting TIMGSN Client...")
time.sleep(5)
self.operate()
{
"norad": 39446,
"name": "UWE-4",
"name": "UWE-3",
"tle_l1": "",
"tle_l2": "",
"tle_source": "",
......
{
"norad": 43880,
"name": "UWE-4",
"tle_l1": "",
"tle_l2": "",
"tle_source": "",
"planning_horizon": 86400,
"refresh_rate": 43200,
"plan":[
{
"contact_type": "TELEMETRY",
"station": "jmuw_uhf",
"start": "2021-04-20 13:54:30",
"end": "2021-04-20 14:05:30"
}
]
}
\ No newline at end of file
......@@ -2,22 +2,24 @@ from utils.threading import ThreadedModule
from utils.helpers import setup_required
from app.client import create_response
import datetime
import requests
import json
import logging
import config
import itertools
logger = logging.getLogger(__name__)
class Satellite(ThreadedModule):
def __init__(self, norad, sat_name="", name="") -> None:
def __init__(self, norad, name="", *args, **kwargs) -> None:
super().__init__(name, *args, **kwargs)
self.norad = norad
self.sat_name = sat_name
self.name = name
self.plan = []
self.owner = None
self._is_setup = False
super().__init__(name, *args, **kwargs)
self.planning_horizon = kwargs[
'planning_horizon'] if 'planning_horizon' in kwargs else 86400
def _set_up(self):
if not self.owner:
......@@ -25,12 +27,27 @@ class Satellite(ThreadedModule):
self._is_setup = True
def _tear_down(self):
pass
logger.info(f"{self.name}: Stopping operation...")
def _loop(self):
responses = []
try:
overpasses = self.query_overpasses()
start = datetime.datetime.utcnow()
end = start + datetime.timedelta(seconds=self.planning_horizon)
overpasses = self.query_overpasses(start, end)
plan = self.get_plan(start, end)
# add telemetry request if plan is not available
if len(overpasses) > len(plan):
plan.extend([{
"contact_type": "TELEMETRY"
}] * (len(overpasses) - len(plan)))
# request overpasses
for op, action in zip(overpasses, plan):
response = self.request_overpass(op['uid'],
action['contact_type'])
responses.append(response)
except Exception as err:
logger.error(err)
......@@ -52,6 +69,9 @@ class Satellite(ThreadedModule):
def set_plan(self, plan):
self.plan = plan
def update_plan(self, plan):
self.plan.extend(plan)
def load_from_json(self, filename=""):
data = None
if not filename:
......@@ -78,17 +98,18 @@ class Satellite(ThreadedModule):
self._set_up()
@setup_required
def query_overpasses(self):
def query_overpasses(self, start=None, end=None):
if start is None:
start = datetime.datetime.utcnow()
if end is None:
end = start + datetime.timedelta(seconds=self.planning_horizon)
overpasses = []
for station_name in config.stations:
start_time = datetime.datetime.utcnow()
dt = datetime.timedelta(seconds=self.planning_horizon)
end_time = start_time + dt
data = {
'norad': self.norad,
'station_name': station_name,
'start_time': start_time.strftime(config.time_format),
'end_time': end_time.strftime(config.time_format)
'start_time': start.strftime(config.time_format),
'end_time': end.strftime(config.time_format)
}
response = requests.get(url=config.endpoint_overpasses,
auth=(self.owner.username,
......
from utils.threading import ThreadedModule
from utils.helpers import setup_required
from utils.threading import ThreadedModule
import config
import datetime
import requests
......@@ -8,14 +10,16 @@ import json
logger = logging.getLogger(__name__)
class Station:
class Station(ThreadedModule):
def __init__(self,
name,
latitude=0,
longitude=0,
height=0,
min_el=0) -> None:
self.name = name
min_el=0,
*args,
**kwargs) -> None:
super().__init__(name, *args, **kwargs)
self.latitude = latitude
self.longitutde = longitude
self.height = height
......@@ -23,6 +27,9 @@ class Station:
self.schedule = []
self.owner = None
self._is_setup = False
self.planning_horizon = kwargs[
'planning_horizon'] if 'planning_horizon' in kwargs else 86400
def __str__(self) -> str:
return f"Ground station: {self.name} | lat: {self.latitude} | long: {self.longitutde}"
......@@ -32,6 +39,110 @@ class Station:
raise AttributeError("No owner has been set. Please add one.")
self._is_setup = True
def _tear_down(self):
logger.info(f"{self.name}: Stopping operation...")
def _loop(self):
received_requests = self.fetch_received_requests()
responses = []
for tracking_request in received_requests:
response = self.respond_to_request(tracking_request)
responses.append(response)
def respond_to_request(self, tracking_request):
request_uid = tracking_request['uid']
overpass = tracking_request['overpass']
status = overpass['status'].upper()
start = datetime.datetime.strptime(
overpass['start_date_str'] + ' ' + overpass['start_time_str'],
config.time_format)
end = datetime.datetime.strptime(
overpass['end_date_str'] + ' ' + overpass['end_time_str'],
config.time_format)
busy = self.is_busy(start, end)
response = None
if status == "PENDING" and not busy:
response = self.accept(request_uid)
elif status == "ACCEPTED":
# do nothing
pass
elif status == "REJECTED" and not busy:
response = self.accept(request_uid)
elif status == "SCHEDULED":
# do nothing
pass
elif status == "FAILED":
# do nothing
pass
elif status == "DATA_UPLOADED":
# do nothing
pass
else:
# reject
response = self.reject(request_uid)
return response
def create_request_url(self, request_uid):
return f"{self.url}/{request_uid}"
@setup_required
def accept_request(self, request_uid):
action = {'action': 'accept'}
url = self.create_request_url(request_uid)
response = requests.put(url=url,
auth=(self.owner.username,
self.owner.password),
data=action)
return response
@setup_required
def reject_request(self, request_uid):
action = {'action': 'reject'}
url = self.create_request_url(request_uid)
response = requests.put(url=url,
auth=(self.owner.username,
self.owner.password),
data=action)
return response
@setup_required
def mark_tracked(self, request_uid):
action = {'action': 'tracked'}
url = self.create_request_url(request_uid)
response = requests.put(url=url,
auth=(self.owner.username,
self.owner.password),
data=action)
return response
@setup_required
def mark_failed(self, request_uid):
action = {'action': 'failed'}
url = self.create_request_url(request_uid)
response = requests.put(url=url,
auth=(self.owner.username,
self.owner.password),
data=action)
return response
@setup_required
def fetch_received_requests(self):
tracking_requests = [] # tracking requests for this station
data = {"direction": "received"}
response = requests.get(url=config.endpoint_requests,
auth=(self.owner.username,
self.owner.password),
data=data)
if response.status_code == 200:
received_requests = response['requests']
tracking_requests = list(
filter(
lambda req: req['overpass']['station_name'] == self.name,
received_requests))
return tracking_requests
def set_owner(self, owner):
self.owner = owner
self._set_up()
......@@ -39,8 +150,17 @@ class Station:
def set_schedule(self, schedule):
self.schedule = schedule
def get_schedule(self):
return self.schedule
def get_schedule(self, start=None, end=None):
if start is None and end is None:
return self.schedule
schedule = []
local = self.schedule.get("local")
timgsn = self.schedule.get("timgsn")
local.extend(timgsn)
for action in local:
if action['start'] >= start and action['end'] <= end:
schedule.append(action)
return schedule
def load_from_json(self, filename=""):
data = None
......@@ -86,7 +206,7 @@ class Station:
start = datetime.datetime.utcnow()
if not end:
end = start + datetime.timedelta(days=7)
for satellite in self.user.get_satellites():
for satellite in self.owner.get_satellites():
data = {
'norad': satellite.norad,
'station_name': self.name,
......@@ -94,8 +214,8 @@ class Station:
'end_time': end.strftime(config.time_format)
}
response = requests.get(url=config.endpoint_overpasses,
auth=(self.user.username,
self.user.password),
auth=(self.owner.username,
self.owner.password),
data=data)
if response.json():
overpasses = response.json()['overpasses']
......@@ -109,9 +229,9 @@ class Station:
'end_time': end.strftime(config.time_format)
}
schedules = requests.get(config.endpoint_scheduler,
auth=(self.user.username,
self.user.password),
data=data)
auth=(self.owner.username,
self.owner.password),
data=data)
schedules.extend(
list(
......
from app.models.satellites import Satellite
from app.models.stations import Station
import functools
import logging
import os
logger = logging.getLogger(__name__)
def authentication_required(f):
@functools.wraps(f)
def wrapper_decorator(user, *args, **kwargs):
......@@ -30,6 +33,15 @@ class User:
def __str__(self) -> str:
return f"User: {self._username}"
def _tear_down(self):
logger.info(f"{self.name}: Stopping operations...")
logger.info(f"{self.name}: Stopping satellite operations...")
for satellite in self.satellites:
satellite.kill()
logger.info(f"{self.name}: Stopping ground station operations...")
for station in self.stations:
station.kill()
@property
def username(self):
return self._username
......@@ -43,15 +55,13 @@ class User:
@property
def password(self):
return self._password
@password.setter
def password(self, password):
if password is None:
raise ValueError("Password needs to be a valid string.")
self._password = password
logger.debug("user password is being set")
self.is_authenticated = True
logger.debug(f"User has been authenticated: {self.is_authenticated}")
def load_credentials_from_env(self, ENV_USERNAME, ENV_PASSWORD):
self._username = os.getenv(ENV_USERNAME)
......@@ -62,7 +72,36 @@ class User:
"were not set in Environment.")
return True
def load_data(self, user_data):
success = True
# set current project
self.set_current_project(user_data.get("current_project"))
# extract satellites
if 'satellites' in user_data:
for norad in user_data.get("satellites"):
satellite = Satellite(norad=norad)
try:
satellite.load_from_json()
except Exception as err:
success = False
logger.exception(err)
self.add_satellite(satellite)
# extract station data
if 'stations' in user_data:
for station_name in user_data.get("stations"):
station = Station(name=station_name)
try:
station.load_from_json()
except Exception as err:
success = False
logger.exception(err)
self.add_station(station)
return success
def authenticate(self, username, password):
logger.info("Authenticating user...")
if username == self._username and password == self._password:
self.is_authenticated = True
return True
......@@ -105,7 +144,7 @@ class User:
for satellite in satellites:
satellite.set_owner(self)
self.satellites.extend(satellites)
def add_request_sender(self, request_sender):
self.request_sender = request_sender
request_sender.add_user(self)
......@@ -113,18 +152,19 @@ class User:
def add_request_handler(self, request_handler):
self.request_handler = request_handler
request_handler.add_user(self)
def set_current_project(self, project_name):
self.current_project = project_name
def get_current_project(self):
return self.current_project
@authentication_required
def operate(self):
self.request_handler.start()
self.request_sender.start()
for satellite in self.satellites:
satellite.start()
for station in self.stations:
station.start()
def terminate(self):
self.request_handler.kill()
self.request_sender.kill()
self._tear_down()
......@@ -7,7 +7,8 @@ basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
# domain name of the TIM Ground Station Network
domain = "https://timgsn.informatik.uni-wuerzburg.de"
# domain = "https://timgsn.informatik.uni-wuerzburg.de"
domain = "http://localhost:5000"
## endpoints
# overpass endpoint: query overpasses here
......
......@@ -6,42 +6,9 @@
"password_env": "TIMGSN_CLIENT_USER1_PASSWORD",
"satellites": [39446, 43880],
"stations": ["jmuw_uhf"],
"data": {
"payload": {},
"telemetry": {}
},
"operations": {
"requests": {
"sender": {
"planning_horizon": 86400,
"refresh_rate": 10000
},
"receiver": {
"handling_rate": 10,
"refresh_rate": 10000
}
},
"overpasses": {}
}
"curent_project": "test_project"
}
],
"data": {
"payload": {},
"telemetry": {}
},
"operations": {
"requests": {
"sender": {
"planning_horizon": 86400,
"refresh_rate": 10000
},
"receiver": {
"handling_rate": 10,
"refresh_rate": 10000
}
},
"overpasses": {}
}
]
},
"gui": {
"general": {},
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment