-
Notifications
You must be signed in to change notification settings - Fork 130
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
[GH-971] Support Jira migration to Oauth #995
Conversation
* [MI-3620] Support Jira migration to Oauth: - Added logic to store the JWT instance data while installing the Oauth instance. - Added logic to fetch the client for JWT instance in case the user is not connected to Oauth. * [MI-3620] Review fixes: Updated log messages * [MI-3620] Review fixes
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #995 +/- ##
==========================================
- Coverage 29.65% 29.59% -0.06%
==========================================
Files 52 52
Lines 7794 7823 +29
==========================================
+ Hits 2311 2315 +4
- Misses 5287 5311 +24
- Partials 196 197 +1
☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work on this @raghavaggarwal2308 👍
In general LGTM, just some comments about variable names and avoiding panics
Another thing that comes to mind is the webhook events for subscriptions, particularly comment events. Does this PR make it so when there is a comment event and the instance is cloud-oauth
, do we:
- if comment author is connected, use their token to fetch the comment
- else if
JWTInstance != nil
use the "bot" to fetch the comment
server/instances.go
Outdated
if jwtInstance.Common().Type == CloudInstanceType { | ||
instance.(*cloudOAuthInstance).JWTInstance = jwtInstance.(*cloudInstance) | ||
} else if jwtInstance.Common().Type == CloudOAuthInstanceType { | ||
instance.(*cloudOAuthInstance).JWTInstance = jwtInstance.(*cloudOAuthInstance).JWTInstance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a bit weird to me but also makes sense. Is the point of this line is to make sure we handle the case where we run /jira instance install cloud-oauth
twice? We "carry over" the JWT instance embedded inside the existing one. This seems a bit error-prone since this is the only field we're carrying over. Are there any other fields we want to retain when "over-installing" a cloud-oauth
over an existing cloud-oauth
instance?
Maybe put a comment here stating this is here to make sure we carry it over when we install cloud-oauth
twice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like a null pointer is not possible here but still a little concerned about it. Ideally we always do the ok
syntax to avoid type assertion panics
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mickmister This function "installInstance" is being called from the "installCloudOAuthInstance" function. After running the install command "installCloudOAuthInstance" is called twice during the flow:
- When we run the install command.
- When we submit the clientID and clientSecret modal. (To store the credentials in the instance data)
The instance is stored in the KV store after running the install command and the JWT instance data is stored inside it (Now the previous JWT instance data is overridden by the oauth instance data). When the function is called on submitting the modal, if we don't carry forward the JWT instance data then it will be lost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the function is called on submitting the modal, if we don't carry forward the JWT instance data then it will be lost.
Yeah when reviewing the OAuth implementation I thought it was weird how we call "install" when submitting the credential form. Being able to run /jira install
on an existing instance was also introduced as a result, which also seemed off.
The fact that we have to manually add the JWTInstance
here without making sure we copy everything over is a little concerning. If we add more fields to the cloud-oauth
instance, we have to update this piece of code, and that seems error-prone.
Also I wonder if there are unwanted side effects to running /jira install server myurl
after an existing Jira Server has been installed with that same URL. We may potentially lose information on the existing struct.
I think we can make the code more defensive by changing the "clientSecret modal" logic to be:
if existingInstance != nil {
existingInstance.ClientID = clientID
existingInstance.ClientSecret = clientSecret
storeInstance(existingInstance)
} else {
newInstance := makeNewInstance()
installInstance(newInstance)
}
And then disallow calling installInstance
with an existing instance, just as it was before the OAuth code was introduced. /jira install
can just start a setup flow if an instance exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raghavaggarwal2308 What do you think about my comment above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mickmister I agree with your suggestion. I have made the necessary changes according to the same.
Note: I have updated the condition to override the enterprise check to work only when a user is migrating from JWT to OAuth. Now if we try to run the "/jira install cloud-oauth ..." OR "/jira install server ..." command twice then we will get that message "You need a valid enterprise licence ....".
In both cases the comment author needs to be connected. Do you want us to modify the logic to use the "bot" to fetch the comment in the second case? |
@raghavaggarwal2308 I'm thinking let's support a third case of "user is not connected to |
server/instance_cloud_oauth.go
Outdated
func (ci *cloudOAuthInstance) getClientForConnection(connection *Connection) (*jira.Client, *http.Client, error) { | ||
oauth2Conf := ci.GetOAuthConfig() | ||
ctx := context.Background() | ||
tokenSource := oauth2Conf.TokenSource(ctx, connection.OAuth2Token) | ||
if ci.JWTInstance != nil && tokenSource == nil { | ||
ci.Plugin.API.LogDebug("Returning a JWT token client in case the stored JWT instance is not nil and the user's oauth token is nil") | ||
if ci.JWTInstance != nil && connection.OAuth2Token == nil { | ||
ci.Plugin.API.LogDebug("Returning a JWT token client since the stored JWT instance is not nil and the user's oauth token is nil") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, the connection
variable here is ambiguous whether the user's connection is cloud
or cloud-oauth
. Maybe put a comment here that says "Checking if this user's connection is for a JWT instance"
Something else comes to mind. If the user disconnects their account, we should make sure we do proper cleanup of anything related to JWT instance type, in case that was their connection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something else comes to mind. If the user disconnects their account, we should make sure we do proper cleanup of anything related to JWT instance type, in case that was their connection
@mickmister We are deleting the user's connection when a user is disconnecting. So, I think that should cover what you are suggesting here.
We cannot wipe the JWT instance data completely because even if a user has disconnected their account, there might be other users who are still using JWT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot wipe the JWT instance data completely because even if a user has disconnected their account, there might be other users who are still using JWT.
Right, I worded "we should make sure we do proper cleanup of anything related to JWT instance type" a bit misleadingly. I really meant cleaning up the connection itself, and not the centralized JWT instance data. LGTM 👍
- Added fallback to use bot when comment author is not connected.
@mickmister I have added the fallback. Just to avoid any confusion, the fallback will only work if there is stored JWT instance as the bot uses JWT for authentication. |
@mickmister I don't have much knowledge about the token handling in the JIRA plugin. Would you mind asking another person fro a review? |
|
||
// Checking if this user's connection is for a JWT instance | ||
if ci.JWTInstance != nil && connection.OAuth2Token == nil { | ||
ci.Plugin.API.LogDebug("Returning a JWT token client since the stored JWT instance is not nil and the user's oauth token is nil") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you thinking we should remove log statements like this in a following release?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mickmister Yes, we will remove these in a later release. These are just to check if the code executed as we expected it to.
server/instances.go
Outdated
if jwtInstance.Common().Type == CloudInstanceType { | ||
instance.(*cloudOAuthInstance).JWTInstance = jwtInstance.(*cloudInstance) | ||
} else if jwtInstance.Common().Type == CloudOAuthInstanceType { | ||
instance.(*cloudOAuthInstance).JWTInstance = jwtInstance.(*cloudOAuthInstance).JWTInstance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raghavaggarwal2308 What do you think about my comment above?
- Updated the code to not install the instance twice during setup. - Updated logic to override the enterprise check only when the user is migrating from JWT to OAuth.
@mickmister Fixed the review comments mentioned by you |
Hi @raghavaggarwal2308 I was going to request some changes, namely moving the backwards compatible For some reason, GitHub is not allowing me to add commits to your branch. Can you please merge the changes from https://github.com/mattermost/mattermost-plugin-jira/tree/MM-971-refactor into your branch? Please leave any comments you have on those changes on this PR. Thank you |
@mickmister Added a few minor comments, otherwise LGTM. |
Co-authored-by: Michael Kochell <[email protected]>
|
||
instance, ok := existingInstance.(*cloudOAuthInstance) | ||
if !ok { | ||
return "", nil, nil, errors.Errorf("existing instance is not a cloud-oauth instance. ID: %s", jiraURL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does the submitCreateCloudOAuthInstance
assume that there's already an existing instance of cloud-oauth? Isn't the submitCreateCloudOAuthInstance
what get's triggered once the admin has clicked the submit button? That should work regardless of whether there is an existing instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
submitCreateCloudOAuthInstance
should really be renamed submitCloudOAuthInstanceConfiguration
or something similar. It's just a form that contains the client id & client secret. The instance is being initialized in the kv store here
mattermost-plugin-jira/server/setup_flow.go
Line 583 in 06a02e8
func (p *Plugin) initCreateCloudOAuthInstance(f *flow.Flow, submission map[string]interface{}) (flow.Name, flow.State, map[string]string, error) { |
instance.JiraClientID = clientID | ||
instance.JiraClientSecret = clientSecret | ||
if err = p.instanceStore.StoreInstance(instance); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we storing the clientID and clientSecret of the new oauth instance to the existing one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's not really a "new" instance here. There will always be an existing one in the kv store at this point of the setup. There are multiple stages to the setup flow and this is the second one
JiraClientID: clientID, | ||
JiraClientSecret: clientSecret, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@esarafianou This is because at the time of "installing" the cloud-oauth
instance, we haven't gotten to the point of the setup where we provide the client id & client secret. That is instead in the second step of the setup process. Before this PR, we were calling installCloudOAuthInstance
on that step too, though we have changed how that works by just setting the values on the saved instance rather than going through that same function call again
mattermost-plugin-jira/server/setup_flow.go
Lines 646 to 648 in 06a02e8
instance.JiraClientID = clientID | |
instance.JiraClientSecret = clientSecret | |
if err = p.instanceStore.StoreInstance(instance); err != nil { |
Summary
What to test?
Steps to reproduce:
Ticket Link
Fixes #971