class Client: def __init__(self): # AWS cognito account info imported from .env dotenv_path = join(dirname(__file__), '.client_env') load_dotenv(dotenv_path) self.region = os.environ.get('client_REGION') self.userpool_id = os.environ.get('client_USERPOOL_ID') self.app_id_client = os.environ.get('client_APP_CLIENT_ID') self.app_client_secret = os.environ.get('client_APP_CLIENT_SECRET') self.username = os.environ.get('client_USERNAME') self.password = os.environ.get('client_PASS') self.user = Cognito(self.userpool_id, self.app_id_client, client_secret=self.app_client_secret, username=self.username, user_pool_region=self.region) self.user.authenticate(password=self.password) def base_url(self, port): local = f"http://localhost:{port}" eb = "http://api.photonranch.org" eb1 = "http://ptr-api.us-east-1.elasticbeanstalk.com" return local def make_authenticated_header(self): header = {} try: self.user.check_token() header["Authorization"] = f"Bearer {self.user.access_token}" except AttributeError as e: print(e) return header def get(self, uri, payload=None, port=5000): header = self.make_authenticated_header() if payload is None: response = requests.get(f"{self.base_url(port)}/{uri}", headers=header) else: response = requests.get(f"{self.base_url(port)}/{uri}", data=json.dumps(payload), headers=header) return response.json() def put(self, uri, payload, port=5000): ''' Localhost put request at the specified uri and access token. Args: uri (str): the part of the url after the port. Eg: 'site1/status/'. payload (dict): body that will be converted to a json string. port (int): optional, specifies localhost port. Return: json response from the request. ''' header = self.make_authenticated_header() response = requests.put(f"{self.base_url(port)}/{uri}", data=json.dumps(payload), headers=header) return response.json() def post(self, uri, payload, port=5000): ''' Localhost post request at the specified uri and access token. Args: uri (str): the part of the url after the port. Eg: 'site1/status/'. payload (dict): body that will be converted to a json string. port (int): optional, specifies localhost port. Return: json response from the request. ''' header = self.make_authenticated_header() response = requests.post(f"{self.base_url(port)}/{uri}", data=json.dumps(payload), headers=header) return response.json()
class CognitoAuthTestCase(unittest.TestCase): def setUp(self): if env('USE_CLIENT_SECRET') == 'True': self.app_id = env('COGNITO_APP_WITH_SECRET_ID') self.client_secret = env('COGNITO_CLIENT_SECRET') else: self.app_id = env('COGNITO_APP_ID') self.client_secret = None self.cognito_user_pool_id = env('COGNITO_USER_POOL_ID') self.username = env('COGNITO_TEST_USERNAME') self.password = env('COGNITO_TEST_PASSWORD') self.user = Cognito(self.cognito_user_pool_id, self.app_id, username=self.username, client_secret=self.client_secret) def test_authenticate(self): self.user.authenticate(self.password) self.assertNotEqual(self.user.access_token, None) self.assertNotEqual(self.user.id_token, None) self.assertNotEqual(self.user.refresh_token, None) def test_verify_token(self): self.user.authenticate(self.password) bad_access_token = '{}wrong'.format(self.user.access_token) with self.assertRaises(TokenVerificationException) as vm: self.user.verify_token(bad_access_token, 'access_token', 'access') # def test_logout(self): # self.user.authenticate(self.password) # self.user.logout() # self.assertEqual(self.user.id_token,None) # self.assertEqual(self.user.refresh_token,None) # self.assertEqual(self.user.access_token,None) @patch('warrant.Cognito', autospec=True) def test_register(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.add_base_attributes(given_name='Brian', family_name='Jones', name='Brian Jones', email='*****@*****.**', phone_number='+19194894555', gender='Male', preferred_username='******') res = u.register('sampleuser', 'sample4#Password') #TODO: Write assumptions def test_renew_tokens(self): self.user.authenticate(self.password) self.user.renew_access_token() @patch('warrant.Cognito', autospec=True) def test_update_profile(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.authenticate(self.password) u.update_profile({'given_name': 'Jenkins'}) # kyhau: Ignore this test as the IAM for testing does not have admin_get_user permission granted. @unittest.expectedFailure def test_admin_get_user(self): u = self.user.admin_get_user() self.assertEqual(u.pk, self.username) def test_check_token(self): self.user.authenticate(self.password) self.assertFalse(self.user.check_token()) @patch('warrant.Cognito', autospec=True) def test_validate_verification(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.validate_verification('4321') @patch('warrant.Cognito', autospec=True) def test_confirm_forgot_password(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.confirm_forgot_password('4553', 'samplepassword') with self.assertRaises(TypeError) as vm: u.confirm_forgot_password(self.password) @patch('warrant.Cognito', autospec=True) def test_change_password(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.authenticate(self.password) u.change_password(self.password, 'crazypassword$45DOG') with self.assertRaises(TypeError) as vm: self.user.change_password(self.password) def test_set_attributes(self): u = Cognito(self.cognito_user_pool_id, self.app_id) u._set_attributes({'ResponseMetadata': { 'HTTPStatusCode': 200 }}, {'somerandom': 'attribute'}) self.assertEqual(u.somerandom, 'attribute') # kyhau: Ignore this test as the IAM for testing does not have admin_authenticate permission granted. @unittest.expectedFailure def test_admin_authenticate(self): self.user.admin_authenticate(self.password) self.assertNotEqual(self.user.access_token, None) self.assertNotEqual(self.user.id_token, None) self.assertNotEqual(self.user.refresh_token, None)
class CognitoAuthTestCase(unittest.TestCase): def setUp(self): if env('USE_CLIENT_SECRET') == 'True': self.app_id = env('COGNITO_APP_WITH_SECRET_ID', 'app') self.client_secret = env('COGNITO_CLIENT_SECRET') else: self.app_id = env('COGNITO_APP_ID', 'app') self.client_secret = None self.cognito_user_pool_id = env('COGNITO_USER_POOL_ID', 'us-east-1_123456789') self.username = env('COGNITO_TEST_USERNAME', 'bob') self.password = env('COGNITO_TEST_PASSWORD', 'bobpassword') self.user = Cognito(self.cognito_user_pool_id, self.app_id, username=self.username, client_secret=self.client_secret) @patch('warrant.aws_srp.AWSSRP.authenticate_user', _mock_authenticate_user) @patch('warrant.Cognito.verify_token', _mock_verify_tokens) def test_authenticate(self): self.user.authenticate(self.password) self.assertNotEqual(self.user.access_token, None) self.assertNotEqual(self.user.id_token, None) self.assertNotEqual(self.user.refresh_token, None) @patch('warrant.aws_srp.AWSSRP.authenticate_user', _mock_authenticate_user) @patch('warrant.Cognito.verify_token', _mock_verify_tokens) def test_verify_token(self): self.user.authenticate(self.password) bad_access_token = '{}wrong'.format(self.user.access_token) with self.assertRaises(TokenVerificationException): self.user.verify_token(bad_access_token, 'access_token', 'access') # def test_logout(self): # self.user.authenticate(self.password) # self.user.logout() # self.assertEqual(self.user.id_token,None) # self.assertEqual(self.user.refresh_token,None) # self.assertEqual(self.user.access_token,None) @patch('warrant.Cognito', autospec=True) def test_register(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.add_base_attributes(given_name='Brian', family_name='Jones', name='Brian Jones', email='*****@*****.**', phone_number='+19194894555', gender='Male', preferred_username='******') u.register('sampleuser', 'sample4#Password') # TODO: Write assumptions @patch('warrant.aws_srp.AWSSRP.authenticate_user', _mock_authenticate_user) @patch('warrant.Cognito.verify_token', _mock_verify_tokens) @patch('warrant.Cognito._add_secret_hash', return_value=None) def test_renew_tokens(self, _): stub = Stubber(self.user.client) # By the stubber nature, we need to add the sequence # of calls for the AWS SRP auth to test the whole process stub.add_response(method='initiate_auth', service_response={ 'AuthenticationResult': { 'TokenType': 'admin', 'IdToken': 'dummy_token', 'AccessToken': 'dummy_token', 'RefreshToken': 'dummy_token' }, 'ResponseMetadata': { 'HTTPStatusCode': 200 } }, expected_params={ 'ClientId': self.app_id, 'AuthFlow': 'REFRESH_TOKEN', 'AuthParameters': { 'REFRESH_TOKEN': 'dummy_token' } }) with stub: self.user.authenticate(self.password) self.user.renew_access_token() stub.assert_no_pending_responses() @patch('warrant.Cognito', autospec=True) def test_update_profile(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.authenticate(self.password) u.update_profile({'given_name': 'Jenkins'}) def test_admin_get_user(self): stub = Stubber(self.user.client) stub.add_response(method='admin_get_user', service_response={ 'Enabled': True, 'UserStatus': 'CONFIRMED', 'Username': self.username, 'UserAttributes': [] }, expected_params={ 'UserPoolId': self.cognito_user_pool_id, 'Username': self.username }) with stub: u = self.user.admin_get_user() self.assertEqual(u.pk, self.username) stub.assert_no_pending_responses() def test_check_token(self): # This is a sample JWT with an expiration time set to January, 1st, 3000 self.user.access_token = ( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG' '9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjMyNTAzNjgwMDAwfQ.C-1gPxrhUsiWeCvMvaZuuQYarkDNAc' 'pEGJPIqu_SrKQ') self.assertFalse(self.user.check_token()) @patch('warrant.Cognito', autospec=True) def test_validate_verification(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.validate_verification('4321') @patch('warrant.Cognito', autospec=True) def test_confirm_forgot_password(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.confirm_forgot_password('4553', 'samplepassword') with self.assertRaises(TypeError): u.confirm_forgot_password(self.password) @patch('warrant.aws_srp.AWSSRP.authenticate_user', _mock_authenticate_user) @patch('warrant.Cognito.verify_token', _mock_verify_tokens) @patch('warrant.Cognito.check_token', return_value=True) def test_change_password(self, _): # u = cognito_user(self.cognito_user_pool_id, self.app_id, # username=self.username) self.user.authenticate(self.password) stub = Stubber(self.user.client) stub.add_response( method='change_password', service_response={'ResponseMetadata': { 'HTTPStatusCode': 200 }}, expected_params={ 'PreviousPassword': self.password, 'ProposedPassword': '******', 'AccessToken': self.user.access_token }) with stub: self.user.change_password(self.password, 'crazypassword$45DOG') stub.assert_no_pending_responses() with self.assertRaises(ParamValidationError): self.user.change_password(self.password, None) def test_set_attributes(self): u = Cognito(self.cognito_user_pool_id, self.app_id) u._set_attributes({'ResponseMetadata': { 'HTTPStatusCode': 200 }}, {'somerandom': 'attribute'}) self.assertEqual(u.somerandom, 'attribute') # @patch('warrant.Cognito.verify_token', _mock_verify_tokens) def test_admin_authenticate(self): stub = Stubber(self.user.client) # By the stubber nature, we need to add the sequence # of calls for the AWS SRP auth to test the whole process stub.add_response(method='admin_initiate_auth', service_response={ 'AuthenticationResult': { 'TokenType': 'admin', 'IdToken': 'dummy_token', 'AccessToken': 'dummy_token', 'RefreshToken': 'dummy_token' } }, expected_params={ 'UserPoolId': self.cognito_user_pool_id, 'ClientId': self.app_id, 'AuthFlow': 'ADMIN_NO_SRP_AUTH', 'AuthParameters': { 'USERNAME': self.username, 'PASSWORD': self.password } }) with stub: self.user.admin_authenticate(self.password) self.assertNotEqual(self.user.access_token, None) self.assertNotEqual(self.user.id_token, None) self.assertNotEqual(self.user.refresh_token, None) stub.assert_no_pending_responses()
class CognitoAuthTestCase(unittest.TestCase): def setUp(self): self.cognito_user_pool_id = env('COGNITO_USER_POOL_ID') self.app_id = env('COGNITO_APP_ID') self.username = env('COGNITO_TEST_USERNAME') self.password = env('COGNITO_TEST_PASSWORD') self.user = Cognito(self.cognito_user_pool_id, self.app_id, self.username) def tearDown(self): del self.user def test_authenticate(self): self.user.authenticate(self.password) self.assertNotEqual(self.user.access_token, None) self.assertNotEqual(self.user.id_token, None) self.assertNotEqual(self.user.refresh_token, None) def test_logout(self): self.user.authenticate(self.password) self.user.logout() self.assertEqual(self.user.id_token, None) self.assertEqual(self.user.refresh_token, None) self.assertEqual(self.user.access_token, None) @patch('warrant.Cognito', autospec=True) def test_register(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) res = u.register('sampleuser', 'sample4#Password', given_name='Brian', family_name='Jones', name='Brian Jones', email='*****@*****.**', phone_number='+19194894555', gender='Male', preferred_username='******') #TODO: Write assumptions def test_renew_tokens(self): self.user.authenticate(self.password) self.user.renew_access_token() def test_update_profile(self): self.user.authenticate(self.password) self.user.update_profile({'given_name': 'Jenkins'}) u = self.user.get_user() self.assertEquals(u.given_name, 'Jenkins') def test_admin_get_user(self): u = self.user.admin_get_user() self.assertEqual(u.pk, self.username) def test_check_token(self): self.user.authenticate(self.password) self.assertFalse(self.user.check_token()) @patch('warrant.Cognito', autospec=True) def test_validate_verification(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.validate_verification('4321') @patch('warrant.Cognito', autospec=True) def test_confirm_forgot_password(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.confirm_forgot_password('4553', 'samplepassword') with self.assertRaises(TypeError) as vm: u.confirm_forgot_password(self.password) @patch('warrant.Cognito', autospec=True) def test_change_password(self, cognito_user): u = cognito_user(self.cognito_user_pool_id, self.app_id, username=self.username) u.authenticate(self.password) u.change_password(self.password, 'crazypassword$45DOG') with self.assertRaises(TypeError) as vm: self.user.change_password(self.password) def test_set_attributes(self): u = Cognito(self.cognito_user_pool_id, self.app_id) u._set_attributes({'ResponseMetadata': { 'HTTPStatusCode': 200 }}, {'somerandom': 'attribute'}) self.assertEquals(u.somerandom, 'attribute') def test_admin_authenticate(self): self.user.admin_authenticate(self.password) self.assertNotEqual(self.user.access_token, None) self.assertNotEqual(self.user.id_token, None) self.assertNotEqual(self.user.refresh_token, None)
class PyEmVue(object): def __init__(self): self.username = None self.token_storage_file = None self.customer = None self.cognito = None def get_devices(self): """Get all devices under the current customer account.""" url = API_ROOT + API_CUSTOMER_DEVICES.format( customerGid=self.customer.customer_gid) response = self._get_request(url) response.raise_for_status() devices = [] if response.text: j = response.json() if 'devices' in j: for dev in j['devices']: devices.append(VueDevice().from_json_dictionary(dev)) if 'devices' in dev: for subdev in dev['devices']: devices.append( VueDevice().from_json_dictionary(subdev)) return devices def populate_device_properties(self, device): """Get details about a specific device""" url = API_ROOT + API_DEVICE_PROPERTIES.format( deviceGid=device.device_gid) response = self._get_request(url) response.raise_for_status() if response.text: j = response.json() device.populate_location_properties_from_json(j) return device def get_customer_details(self): """Get details for the current customer.""" url = API_ROOT + API_CUSTOMER.format(email=self.username) response = self._get_request(url) response.raise_for_status() if response.text: j = response.json() return Customer().from_json_dictionary(j) return None def get_total_usage(self, channel, timeFrame=TotalTimeFrame.ALL.value, unit=TotalUnit.WATTHOURS.value): """Get total usage over the provided timeframe for the given device channel.""" url = API_ROOT + API_USAGE_TOTAL.format(deviceGid=channel.device_gid, timeFrame=timeFrame, unit=unit, channels=channel.channel_num) response = self._get_request(url) response.raise_for_status() if response.text: j = response.json() if 'usage' in j: return j['usage'] return 0 def get_usage_over_time(self, channel, start, end, scale=Scale.SECOND.value, unit=Unit.WATTS.value): """Get usage over the given time range. Used for primarily for plotting history. Supports time scales less than DAY.""" if scale != Scale.SECOND.value and scale != Scale.MINUTE.value and scale != Scale.MINUTES_15.value and scale != Scale.HOUR.value: raise ValueError( f'Scale of {scale} is invalid, must be 1S, 1MIN, 15MIN, or 1H.' ) url = API_ROOT + API_USAGE_TIME.format(deviceGid=channel.device_gid, startTime=_format_time(start), endTime=_format_time(end), scale=scale, unit=unit, channels=channel.channel_num) response = self._get_request(url) response.raise_for_status() if response.text: j = response.json() if 'usage' in j: return j['usage'] return [] def get_usage_over_date_range(self, channel, start, end, scale=Scale.DAY.value, unit=Unit.WATTS.value): """ Get usage over the given date range. Used for primarily for plotting history. Supports time scales of DAY or larger. Note strange behavior with what day is which, first value is for day before start, last value is for day before end, for test timezone of UTC-4. Advise getting a range of days around the desired day and manual verification. """ if scale != Scale.DAY.value and scale != Scale.WEEK.value and scale != Scale.MONTH.value and scale != Scale.YEAR.value: raise ValueError( f'Scale of {scale} is invalid, must be 1D, 1W, 1MON, or 1Y.') url = API_ROOT + API_USAGE_DATE.format(deviceGid=channel.device_gid, startDate=_format_date(start), endDate=_format_date(end), scale=scale, unit=unit, channels=channel.channel_num) response = self._get_request(url) response.raise_for_status() if response.text: j = response.json() if 'usage' in j: return j['usage'] return [] def get_recent_usage(self, scale=Scale.HOUR.value, unit=Unit.WATTS.value): """Get usage over the last 'scale' timeframe.""" now = datetime.datetime.utcnow() return self.get_usage_for_time_scale(now, scale, unit)[0] def get_usage_for_time_scale(self, time, scale=Scale.HOUR.value, unit=Unit.WATTS.value): """ Get usage for the 'scale' timeframe ending at the given time. Only supported for scales less than one day, otherwise time value is ignored and most recent data is given. """ start = time - datetime.timedelta(seconds=1) end = time url = API_ROOT + API_USAGE_DEVICES.format( customerGid=self.customer.customer_gid, scale=scale, unit=unit, startTime=_format_time(start), endTime=_format_time(end)) response = self._get_request(url) response.raise_for_status() channels = [] realStart = None realEnd = None if response.text: j = response.json() if 'start' in j: realStart = parse(j['start']) if 'end' in j: realEnd = parse(j['end']) if 'channels' in j: for channel in j['channels']: channels.append( VuewDeviceChannelUsage().from_json_dictionary(channel)) return channels, realStart, realEnd def login(self, username=None, password=None, id_token=None, access_token=None, refresh_token=None, token_storage_file=None): """ Authenticates the current user using access tokens if provided or username/password if no tokens available. Provide a path for storing the token data that can be used to reauthenticate without providing the password. Tokens stored in the file are updated when they expire. """ # Use warrant to go through the SRP authentication to get an auth token and refresh token client = boto3.client( 'cognito-idp', region_name='us-east-2', config=botocore.client.Config(signature_version=botocore.UNSIGNED)) if id_token is not None and access_token is not None and refresh_token is not None: # use existing tokens self.cognito = Cognito(USER_POOL, CLIENT_ID, user_pool_region='us-east-2', id_token=id_token, access_token=access_token, refresh_token=refresh_token) self.cognito.client = client elif username is not None and password is not None: #log in with username and password self.cognito = Cognito(USER_POOL, CLIENT_ID, user_pool_region='us-east-2', username=username) self.cognito.client = client self.cognito.authenticate(password=password) else: raise Exception( 'No authentication method found. Must supply username/password or id/auth/refresh tokens.' ) if self.cognito.access_token is not None: if token_storage_file is not None: self.token_storage_file = token_storage_file self._check_token() user = self.cognito.get_user() self.username = user._data['email'] self.customer = self.get_customer_details() self._store_tokens() return self.customer is not None def _check_token(self): if self.cognito.check_token(renew=True): # Token expired and we renewed it. Store new token self._store_tokens() def _store_tokens(self): if not self.token_storage_file: return data = { 'idToken': self.cognito.id_token, 'accessToken': self.cognito.access_token, 'refreshToken': self.cognito.refresh_token } if self.username: data['email'] = self.username with open(self.token_storage_file, 'w') as f: json.dump(data, f, indent=2) def _get_request(self, full_endpoint): if not self.cognito: raise Exception( 'Must call "login" before calling any API methods.') self._check_token( ) # ensure our token hasn't expired, refresh if it has headers = {'authtoken': self.cognito.id_token} return requests.get(full_endpoint, headers=headers)