Coverage for functions/authentication.py: 100%
85 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-08-28 12:30 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-08-28 12:30 -0700
1"""
2Authentication class.
3"""
5# └── functions/authentication.py
6# ├── [API] get_token()
7# ├── [API] check_token()
8# ├── [API] request_handling()
9# └── [API] request()
11import json
12from html.parser import HTMLParser
13import requests
16class HTMLResponseParser(HTMLParser):
17 """Response parser for HTML content."""
18 def __init__(self):
19 super().__init__()
20 self.is_header = False
21 self.headers = []
23 def read(self, data):
24 """Read the headers from the HTML content."""
25 self.is_header = False
26 self.headers = []
27 self.reset()
28 self.feed(data)
29 return " ".join(self.headers)
31 def handle_starttag(self, tag, attrs):
32 """Detect headers."""
33 if tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
34 self.is_header = True
36 def handle_data(self, data):
37 """Add header data to the list."""
38 if self.is_header:
39 self.headers.append(data.strip())
40 self.is_header = False
43class Authentication():
44 """Authentication class for FarmBot API."""
45 def __init__(self, state):
46 self.state = state
48 def get_token(self, email, password, server="https://my.farm.bot"):
49 """Get FarmBot authorization token. Server is 'https://my.farm.bot' by default."""
51 try:
52 headers = {'content-type': 'application/json'}
53 user = {'user': {'email': email, 'password': password}}
54 response = requests.post(f'{server}/api/tokens', headers=headers, json=user)
55 # Handle HTTP status codes
56 if response.status_code == 200:
57 self.state.token = response.json()
58 self.state.error = None
59 self.state.print_status(description=f"Successfully fetched token from {server}.")
60 return response.json()
61 elif response.status_code == 404:
62 self.state.error = "HTTP ERROR: The server address does not exist."
63 elif response.status_code == 422:
64 self.state.error = "HTTP ERROR: Incorrect email address or password."
65 else:
66 self.state.error = f"HTTP ERROR: Unexpected status code {response.status_code}"
67 # Handle DNS resolution errors
68 except requests.exceptions.RequestException as e:
69 if isinstance(e, requests.exceptions.ConnectionError):
70 self.state.error = "DNS ERROR: The server address does not exist."
71 elif isinstance(e, requests.exceptions.Timeout):
72 self.state.error = "DNS ERROR: The request timed out."
73 elif isinstance(e, requests.exceptions.RequestException):
74 self.state.error = "DNS ERROR: There was a problem with the request."
75 except Exception as e:
76 self.state.error = f"DNS ERROR: An unexpected error occurred: {str(e)}"
78 self.state.token = None
79 self.state.print_status(description=self.state.error)
80 return self.state.error
82 def request_handling(self, response):
83 """Handle errors associated with different endpoint errors."""
85 error_messages = {
86 404: "The specified endpoint does not exist.",
87 400: "The specified ID is invalid or you do not have access to it.",
88 401: "The user`s token has expired or is invalid.",
89 502: "Please check your internet connection and try again."
90 }
92 # Handle HTTP status codes
93 if response.status_code == 200:
94 self.state.print_status(description="Successfully sent request via API.")
95 return 200
96 if 400 <= response.status_code < 500:
97 self.state.error = f"CLIENT ERROR {response.status_code}: {error_messages.get(response.status_code, response.reason)}"
98 elif 500 <= response.status_code < 600:
99 self.state.error = f"SERVER ERROR {response.status_code}: {response.text}"
100 else:
101 self.state.error = f"UNEXPECTED ERROR {response.status_code}: {response.text}"
103 try:
104 response.json()
105 except requests.exceptions.JSONDecodeError:
106 if '<html>' in response.text:
107 parser = HTMLResponseParser()
108 self.state.error += f" ({parser.read(response.text)})"
109 else:
110 self.state.error += f" ({response.text})"
111 else:
112 self.state.error += f" ({json.dumps(response.json(), indent=2)})"
114 self.state.print_status(description=self.state.error)
115 return response.status_code
117 def request(self, method, endpoint, database_id, payload=None):
118 """Make requests to API endpoints using different methods."""
120 self.state.check_token()
122 # use 'GET' method to view endpoint data
123 # use 'POST' method to overwrite/create new endpoint data
124 # use 'PATCH' method to edit endpoint data (used for new logs)
125 # use 'DELETE' method to delete endpoint data (hidden)
127 token = self.state.token["token"]
128 iss = token["unencoded"]["iss"]
130 id_part = "" if database_id is None else f"/{database_id}"
131 url = f'https:{iss}/api/{endpoint}{id_part}'
133 headers = {'authorization': token['encoded'], 'content-type': 'application/json'}
134 response = requests.request(method, url, headers=headers, json=payload)
136 if self.request_handling(response) == 200:
137 self.state.error = None
138 self.state.print_status(description="Successfully returned request contents.")
139 return response.json()
140 self.state.print_status(description="There was an error processing the request...")
141 return self.state.error