Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting Client Secret rotation programatically + documentation update #15

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

pitchmuc
Copy link
Contributor

Client Rotating Option + ConnectInstance Documentation

Providing the option to rotate client secrets for Oauth V2 connection as described in the documentation : https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication/implementation/

At the same time, documenting the ConnectInstance class.

Motivation and Context

When clients wants to change their secret, they may want to do it programmatically and not via UI.
We now support that option via the ConnectInstance object.

How Has This Been Tested?

Tested on client implementation.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@pitchmuc pitchmuc added documentation Improvements or additions to documentation enhancement New feature or request labels Jul 27, 2023
@pitchmuc pitchmuc requested review from rezpe and cmenguy July 27, 2023 14:51
@skanjila skanjila requested a review from Saikat1911 July 27, 2023 14:57
@pitchmuc pitchmuc requested review from skanjila and liqunc and removed request for Saikat1911 July 27, 2023 15:18
@@ -367,3 +370,141 @@ def setSandbox(self,sandbox:str=None)->dict:
self.header["x-sandbox-name"] = sandbox
self.__configObject__["sandbox"] = sandbox
return self.getConfigObject()

def setOauthV2setup(self,credentialId:str=None,orgDevId:str=None)->bool:
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use python style conventions in functions, namely with underscores for functions

Copy link
Contributor Author

@pitchmuc pitchmuc Aug 3, 2023

Choose a reason for hiding this comment

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

I would have agreed but the whole package is using the camelCase notation.
I would think that we should be consistent over the different modules and keep camelCase usage.
It does not hurt in the end.

Also changing the method names is a massive breaking change.

if self.connectionType != "oauthV2":
raise Exception("You are trying to set credential ID or orgDevId for auth that is not OauthV2. We do not support these auth type.")
if credentialId is None:
raise ValueError("credentialId is None")
Copy link
Contributor

Choose a reason for hiding this comment

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

With these exceptions what are the expectations on how the user recovers, what do they need to do

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The rotating client capability are not supported for JWT or Oauth V1.
There is no recovery, except moving to OauthV2 credential type.
How do you recommend to provide that feedback ?

aepp/configs.py Outdated
raise ValueError("orgDevId is None")
self.credentialId = credentialId
self.orgDevId = orgDevId
return True
Copy link
Contributor

Choose a reason for hiding this comment

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

why are we returning boolean from a mutator function, typically a mutator would return void and an accessor function would return a datatype

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, for some reason, I thought it would be interesting to get when the function has correctly executed but definitely not normal setup, I will change it in the next commit.

orgDevId : OPTIONAL : the org Id but NOT the IMS string. It is defined on your project page.
Example : https://developer.adobe.com/console/projects/<orgId>/<projectId>/credentials/<credentialId>/details/oauthservertoserver
"""
if self.connectionType != "oauthV2":
Copy link
Contributor

Choose a reason for hiding this comment

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

should we move these checks into Utils instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure what part you are referring about.
the configs part ? We can, we just need to change the reference everywhere.
If it is in configs.py vs utils.py it does not matter.
Let me know.
I would believe that it would be a different PR to be honest.

'x-api-key' : self.client_id
}
endpoint = f"https://api.adobe.io/console/organizations/{orgDevId}/credentials/{credentialId}/secrets"
res = self.connector.getData(endpoint,headers=myheader)
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to do any validations on res?

@@ -12,7 +12,7 @@
import os
Copy link
Contributor

Choose a reason for hiding this comment

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

please add unit tests to this PR based on our new convention with all features

cf.write(json.dumps(json_data, indent=4))
return True

def deleteSecrete(self,secretUID:str=None,credentialId:str=None,orgDevId:str=None,)->None:
Copy link
Contributor

Choose a reason for hiding this comment

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

lets rename to delete_secret

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As explained above, the package current naming convention is using lower camelCase, so I think we should keep it consistent.
Otherwise, we would need to rename all existing methods which is a massive breaking change.

aepp/configs.py Outdated
}
with open(destination, "w") as cf:
cf.write(json.dumps(json_data, indent=4))
return True
Copy link
Contributor

Choose a reason for hiding this comment

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

again lets not return bool from these functions, typically set and update either return void or a data structure that contains a status

orgDevId : OPTIONAL : the org Id but NOT the IMS string. It is defined on your project page.
Example : https://developer.adobe.com/console/projects/<orgId>/<projectId>/credentials/<credentialId>/details/oauthservertoserver
"""
if self.connectionType != "oauthV2":
Copy link
Contributor

Choose a reason for hiding this comment

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

see my comments above on these if statements

@@ -0,0 +1,258 @@
# Connect Object class

Copy link
Contributor

Choose a reason for hiding this comment

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

can we move this to the Readme or code, not sure we need an individual markdown file, was there a reason to create separate documentation around this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As you can see the ConnectObject Instance is actually a module by itself providing way to connect to the AEP API without the need of other modules, it can also serve as a way to rotate your client secret programmatically.
As a specific class and method available, it has to follow the current structure and get its own markdown file.

The readMe would be pretty long with that.

'Authorization' : 'Bearer '+self.token,
'x-api-key' : self.client_id
}
endpoint = f"https://api.adobe.io/console/organizations/{orgDevId}/credentials/{credentialId}/secrets"
Copy link
Contributor

Choose a reason for hiding this comment

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

will this endpoint be the same for stage and prod?

Copy link
Contributor Author

@pitchmuc pitchmuc Aug 4, 2023

Choose a reason for hiding this comment

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

Good question. I am only able to follow what is actually providing on the official documentation. https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication/implementation/

I do not think that you are using OauthV2, but Oauth V1.
I would suggest that we go live so provide clients with this functionality and if you ever need it and it is required to have a different url, we can use a variable to modify the endpoint automatically.
We can even use the environment parameter from the configuration file or configure method to define the correct endpoint if it turns out to be different from the stage and prod.

@pitchmuc
Copy link
Contributor Author

I have added unit tests and modified the return statement for the void functions as asked.
Let me know if this is now good to go.

from unittest.mock import patch, MagicMock, Mock, ANY

@patch("aepp.configs.importConfigFile")
def test_importConfigFile_Oauth(mock_data):
Copy link
Contributor

Choose a reason for hiding this comment

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

nit change 0auth to oauth


@patch("aepp.configs.importConfigFile")
def test_importConfigFile_Oauth(mock_data):
mock_data.return_value = {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure we need this test, from what I see its just saying that mock_data is a dict, is there a case ever where it wouldnt be a dict?

Copy link
Contributor

Choose a reason for hiding this comment

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

You can just define mock_data as a fixture that can be leveraged in the methods below

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants