Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ on:
- main
- develop
pull_request:
branches:
- main
- develop
workflow_dispatch:

jobs:
Expand Down
11 changes: 5 additions & 6 deletions build.savant
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2024, FusionAuth, All Rights Reserved
* Copyright (c) 2018-2025, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -50,7 +50,7 @@ target(name: "clean", description: "Cleans the build directory") {
target(name: "compile", description: "Builds archives of the source and compiled versions of the code.", dependsOn: ["setup-python"]) {
def proc = "python3 setup.py sdist bdist_wheel".execute()
proc.consumeProcessOutput(System.out, System.err)
proc.waitFor()
assert proc.waitFor() == 0
}

target(name: "int", description: "Releases a local integration build of the project", dependsOn: ["compile"]) {
Expand Down Expand Up @@ -78,11 +78,11 @@ target(name: "test", description: "Runs the project's tests", dependsOn: ["compi
target(name: "setup-python", description: "Gets the python dependencies") {
def proc1 = "python3 -m pip install --user --upgrade setuptools".execute()
proc1.consumeProcessOutput(System.out, System.err)
proc1.waitFor()
assert proc1.waitFor() == 0

def proc2 = "python3 -m pip install --user --upgrade wheel twine requests deprecated".execute()
proc2.consumeProcessOutput(System.out, System.err)
proc2.waitFor()
assert proc2.waitFor() == 0
}

/**
Expand All @@ -104,8 +104,7 @@ target(name: "publish", description: "Publishes source and built versions of the

def process = pb.start()
process.consumeProcessOutput(System.out, System.err)
process.waitFor()
return process.exitValue() == 0
assert process.waitFor() == 0
}

target(name: "release", description: "Releases a full version of the project", dependsOn: ["int"]) {
Expand Down
84 changes: 84 additions & 0 deletions src/main/python/fusionauth/fusionauth_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,18 @@ def comment_on_user(self, request):
.post() \
.go()

def complete_verify_identity(self, request):
"""
Completes verification of an identity using verification codes from the Verify Start API.

Attributes:
request: The identity verify complete request that contains all the information used to verify the identity.
"""
return self.start().uri('/api/identity/verify/complete') \
.body_handler(JSONBodyHandler(request)) \
.post() \
.go()

def complete_web_authn_assertion(self, request):
"""
Complete a WebAuthn authentication ceremony by validating the signature against the previously generated challenge without logging the user in
Expand Down Expand Up @@ -3400,6 +3412,20 @@ def retrieve_user_by_login_id(self, login_id):
.get() \
.go()

def retrieve_user_by_login_id_with_login_id_types(self, login_id, login_id_types):
"""
Retrieves the user for the loginId, using specific loginIdTypes.

Attributes:
login_id: The email or username of the user.
login_id_types: the identity types that FusionAuth will compare the loginId to.
"""
return self.start().uri('/api/user') \
.url_parameter('loginId', self.convert_true_false(login_id)) \
.url_parameter('loginIdTypes', self.convert_true_false(login_id_types)) \
.get() \
.go()

def retrieve_user_by_username(self, username):
"""
Retrieves the user for the given username.
Expand Down Expand Up @@ -3581,6 +3607,27 @@ def retrieve_user_login_report_by_login_id(self, login_id, start, end, applicati
.get() \
.go()

def retrieve_user_login_report_by_login_id_and_login_id_types(self, login_id, start, end, login_id_types, application_id=None):
"""
Retrieves the login report between the two instants for a particular user by login Id, using specific loginIdTypes. If you specify an application id, it will only return the
login counts for that application.

Attributes:
application_id: (Optional) The application id.
login_id: The userId id.
start: The start instant as UTC milliseconds since Epoch.
end: The end instant as UTC milliseconds since Epoch.
login_id_types: the identity types that FusionAuth will compare the loginId to.
"""
return self.start().uri('/api/report/login') \
.url_parameter('applicationId', self.convert_true_false(application_id)) \
.url_parameter('loginId', self.convert_true_false(login_id)) \
.url_parameter('start', self.convert_true_false(start)) \
.url_parameter('end', self.convert_true_false(end)) \
.url_parameter('loginIdTypes', self.convert_true_false(login_id_types)) \
.get() \
.go()

def retrieve_user_recent_logins(self, user_id, offset, limit):
"""
Retrieves the last number of login records for a user.
Expand Down Expand Up @@ -4210,6 +4257,18 @@ def send_two_factor_code_for_login_using_method(self, two_factor_id, request):
.post() \
.go()

def send_verify_identity(self, request):
"""
Send a verification code using the appropriate transport for the identity type being verified.

Attributes:
request: The identity verify send request that contains all the information used send the code.
"""
return self.start().uri('/api/identity/verify/send') \
.body_handler(JSONBodyHandler(request)) \
.post() \
.go()

def start_identity_provider_login(self, request):
"""
Begins a login request for a 3rd party login that requires user interaction such as HYPR.
Expand Down Expand Up @@ -4253,6 +4312,19 @@ def start_two_factor_login(self, request):
.post() \
.go()

def start_verify_identity(self, request):
"""
Start a verification of an identity by generating a code. This code can be sent to the User using the Verify Send API
Verification Code API or using a mechanism outside of FusionAuth. The verification is completed by using the Verify Complete API with this code.

Attributes:
request: The identity verify start request that contains all the information used to begin the request.
"""
return self.start().uri('/api/identity/verify/start') \
.body_handler(JSONBodyHandler(request)) \
.post() \
.go()

def start_web_authn_login(self, request):
"""
Start a WebAuthn authentication ceremony by generating a new challenge for the user
Expand Down Expand Up @@ -4835,6 +4907,18 @@ def verify_email_address_by_user_id(self, request):
.post() \
.go()

def verify_identity(self, request):
"""
Administratively verify a user identity.

Attributes:
request: The identity verify request that contains information to verify the identity.
"""
return self.start().uri('/api/identity/verify') \
.body_handler(JSONBodyHandler(request)) \
.post() \
.go()

@deprecated("This method has been renamed to verify_user_registration and changed to take a JSON request body, use that method instead.")
def verify_registration(self, verification_id):
"""
Expand Down
51 changes: 47 additions & 4 deletions src/test/python/fusionauth/fusionauth_client_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
# Copyright (c) 2024, FusionAuth, All Rights Reserved
# Copyright (c) 2024-2025, FusionAuth, All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -26,9 +50,8 @@

import json
import os
import uuid

import unittest
import uuid

from fusionauth.fusionauth_client import FusionAuthClient

Expand All @@ -51,7 +74,8 @@ def runTest(self):
def test_retrieve_applications(self):
client_response = self.client.retrieve_applications()
self.assertEqual(client_response.status, 200)
self.assertEqual(len(client_response.success_response['applications']), 2)
# tnent manager is 1 application, admin is another, Pied piper in the kickstart is the 3rd.
self.assertEqual(len(client_response.success_response['applications']), 3)

def test_create_user_retrieve_user(self):
# Check if the user already exists.
Expand Down Expand Up @@ -84,6 +108,25 @@ def test_create_user_retrieve_user(self):
self.assertFalse('password' in get_user_response.success_response['user'])
self.assertFalse('salt' in get_user_response.success_response['user'])

# Retrieve the user via loginId
get_user_response = self.client.retrieve_user_by_login_id('[email protected]')
self.assertEqual(get_user_response.status, 200)
self.assertIsNotNone(get_user_response.success_response)
self.assertIsNone(get_user_response.error_response)
self.assertEqual(get_user_response.success_response['user']['email'], '[email protected]')

# Explicit loginIdType
get_user_response = self.client.retrieve_user_by_login_id_with_login_id_types('[email protected]', ['email'])
self.assertEqual(get_user_response.status, 200)
self.assertIsNotNone(get_user_response.success_response)
self.assertIsNone(get_user_response.error_response)
self.assertEqual(get_user_response.success_response['user']['email'], '[email protected]')

# TODO: Once issue 1 is released, this test should pass
# # wrong loginIdType
# get_user_response = self.client.retrieve_user_by_login_id_with_login_id_types('[email protected]', ['phoneNumber'])
# self.assertEqual(get_user_response.status, 404)

def test_retrieve_user_missing(self):
user_id = uuid.uuid4()
client_response = self.client.retrieve_user(user_id)
Expand Down