Skip to content

Conversation

@AnnaShcherenko
Copy link

Issue #16 This PR implements authentication class to use when sending messages

Problem Solved

  • Issue: Library uses fixed request parameters without authentication flexibility
  • Solution: Added AuthTokenManager for dynamic OAuth2 Bearer token authentication
  • Benefit: Supports modern OAuth2 APIs without breaking existing functionality

Changes Made

1. New AuthTokenManager Class (shapeshifter_uftp/token_manager.py)

  • Implements OAuth2 Client Credentials flow (RFC 6749)
  • Automatic token acquisition and refresh
  • Thread-safe token management
  • Configurable refresh timing (default: 30s before expiry)

2. Enhanced ShapeshifterService (shapeshifter_uftp/service/base_service.py)

  • Added optional OAuth2 configuration parameters
  • Creates AuthTokenManager when OAuth2 credentials provided
  • Passes token manager to clients via dependency injection

3. Updated ShapeshifterClient (shapeshifter_uftp/client/base_client.py)

  • Accepts optional oauth_token_manager parameter
  • Dynamically generates headers with Bearer tokens
  • Graceful fallback to basic headers if OAuth2 fails

Usage

Backward Compatible (existing code unchanged):

service = MyShapeshifterService(
    sender_domain="example.com",
    signing_key="key"
)

New OAuth2 Support:

service = MyShapeshifterService(
    sender_domain="example.com", 
    signing_key="key",
    oauth_token_endpoint="https://api.provider.com/oauth2/token",
    oauth_client_id="client_id",
    oauth_client_secret="client_secret"
)
# All outgoing requests now include Bearer tokens automatically

Testing

Unit test file (test_oauth_token_unit.py) covers:

  • OAuth2 token acquisition and refresh
  • Error handling and fallback behavior
  • Service and client parts

Integration tests are not implemented

Comment on lines +120 to +131
# Find the right headers to use for the request. If we have
# an OAuth2 token manager, we will use that to get the
# request headers. If not, we will use the basic Content-Type
try:
if self.oauth_token_manager:
header = self.oauth_token_manager.get_request_headers()
else:
header = {"Content-Type": "text/xml; charset=utf-8"}
except Exception as e:
logger.warning(f"Failed to get OAuth2 headers, falling back to basic headers: {e}")
header = {"Content-Type": "text/xml; charset=utf-8"}

Copy link
Member

@KoviaX KoviaX Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some duplication could be reduced here, along the lines of:

def get_headers(oauth_token_manager: Optional[AuthTokenManager]):
"""If there is an OAuth2 token manager, that will be used to get the request headers. If not, the basic Content-Type is the only header"""
try:
if oauth_token_manager:
return oauth_token_manager.get_request_headers()
except Exception as e:
logger.warning(f"Failed to get OAuth2 headers, falling back to basic headers: {e}")
# Fallback header
return {"Content-Type": "text/xml; charset=utf-8"}

headers = get_headers(self.oauth_token_manager)

:param path: the URL path that the server listens on (default: /shapeshifter/api/v3/message)
:param oauth_token_endpoint: the OAuth2 token endpoint to use for obtaining access tokens
:param oauth_client_id: the OAuth2 client ID to use for obtaining access tokens
:param oauth_client_secret: the OAuth2 client secret to use for obtaining access tokens
Copy link
Member

@KoviaX KoviaX Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's missing the token refresh buffer param here

'client_secret': self.oauth_client_secret
}

headers = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this header duplicated here again, perhaps a constant that's used/imported in multiple places makes sense.

:return: Dictionary of headers
"""
headers = {"Content-Type": "text/xml; charset=utf-8"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment

@KoviaX
Copy link
Member

KoviaX commented Jul 1, 2025

Hello @AnnaShcherenko , thank you for your suggestion/contribution!

We definitely want to add OAuth authentication to this library, and the code you have provided seems to be of good quality and fits the current structure of the application.

This Pull Request has been discussed in the Technical Steering Comittee and I've made some small comments.

@KoviaX
Copy link
Member

KoviaX commented Jul 1, 2025

I also just got informed that one of the original maintainers of the Python library is working on a new version, which includes an OAuth implementation: https://github.com/shapeshifter/shapeshifter-library-python/tree/version-2 perhaps you can see if this fits your use-case as well, as this version is currently being tested with a customer and is likely to be released in the upcoming months.

@AnnaShcherenko
Copy link
Author

Thank you @KoviaX for responding to my request—I truly appreciate that you're considering my code.
Since this feature is quite important to us, could you please share more accurate timelines and expectations for its implementation? If the process is expected to take a significant amount of time, we may need to allocate additional development resources on our end to work around the absence of this functionality.
Looking forward to your insights. Thanks again!

@KoviaX
Copy link
Member

KoviaX commented Jul 1, 2025

Hi @AnnaShcherenko , The current "2.0" implementation is unlikely to undergo major changes before official release besides bugfixes at this point in time. So you can safely make use of the implementation that is currently built in the version-2 branch!

@AnnaShcherenko
Copy link
Author

thank you, @KoviaX

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants