-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Feature: Airtable Analyzer for Personal Access Tokens #3941
base: main
Are you sure you want to change the base?
Feature: Airtable Analyzer for Personal Access Tokens #3941
Conversation
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.
Great job @nabeelalam! Left a few small suggestion and questions, but otherwise looks great.
return nil, err | ||
} | ||
|
||
scopeStatusMap[common.PermissionStrings[common.UserEmailRead]] = userInfo.Email != 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.
question: Is the idea here that if the response from the FetchAirtableUserInfo
API contains an email, they have the user.email:read
scope?
scopeStatusMap[common.PermissionStrings[common.UserEmailRead]] = userInfo.Email != nil | ||
|
||
var basesInfo *common.AirtableBases | ||
if granted, err := determineScope(token, common.SchemaBasesRead, nil); granted { |
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.
suggestion: The way determineScope
is written, I’m not sure there’s a possible code path where granted == true
and err != nil
. As a result, the error check right below this will never be triggered.
Additionally, this approach seems to be swallowing the error—was that intentional? If so, could we add a comment explaining why that design is acceptable? I bring this up because, throughout the rest of the implementation, we return an error if one occurs during an API call. If this is an exception, it should be explicitly documented.
Alternatively, we could remove the inline granted
check and handle the error first.
Ex:
granted, err := determineScope(token, common.SchemaBasesRead, nil)
if err != nil {
return nil, err
}
if granted {
...
}
if resp.StatusCode == http.StatusOK { | ||
scopeStatusMap[scopeString] = true | ||
return true, nil | ||
} else if endpoint.ExpectedErrorResponse != 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.
suggestion: Since the if
block returns, we don’t need an else if
. We can de-indent this and use a regular if
, which improves readability by following the line of sight coding principle. ([Reference](https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea88))
ex:
if resp.StatusCode == http.StatusOK {
scopeStatusMap[scopeString] = true
return true, nil
}
if endpoint.ExpectedErrorResponse != nil {
..
}
scopeStatusMap[scopeString] = true | ||
return true, nil | ||
} else if endpoint.ExpectedErrorResponse != nil { | ||
var result map[string]interface{} |
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.
nit: interface{}
-> any
return false, err | ||
} | ||
|
||
if errorInfo, ok := result["error"].(map[string]interface{}); ok { |
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.
ditto
} | ||
} | ||
|
||
func determineScope(token string, scope common.Permission, ids map[string]string) (bool, error) { |
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.
nit: For consistency and better readability, can we rename scope
to permission
or perm
? Since it's later used as an argument for getEndpointByPermission
, this would make the code clearer.
} | ||
|
||
func determineScopes(token string, basesInfo *common.AirtableBases) error { | ||
if basesInfo != nil && len(basesInfo.Bases) > 0 { |
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.
suggestion: Similar to my earlier "line of sight" comment, we can invert the logic of this if
statement to return early, allowing us to de-indent the following for
loop for better readability.
Ex:
if basesInfo == nil || len(basesInfo.Bases) == 0 {
return nil
}
for _ base := range baseInfo.Bases {
...
}
tableScopesDetermined = true | ||
} | ||
|
||
if granted, err := determineScope(token, common.DataRecordsRead, ids); 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.
suggestion: We can also remove the if else
and just use an if
at the same indentation level, since the if
block already returns.
Ex:
granted, err := determineScope(token, common.DataRecordsRead, ids)
if err != nil {
return err
}
if !granted {
continue
}
...
return err | ||
} else if granted { | ||
// Verifying scopes that require an existing record and record read permission | ||
records, err := fetchAirtableRecords(token, base.ID, table.ID) |
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.
question/suggestion: It looks like we might be again swallowing the error? If this is also done intentionally let's add a comment explaining why. We can make this a little easier to reason about if we check the err and handle it first. We then don't even need to check the len(records)
we can simply range over them.
Ex:
records, err := fetchAirtableRecords(token, base.ID, table.ID)
if err != nil {
return err // or continue
}
for _, record := range records {
ids["recordID"] = record.ID
_, err = determineScope(token, common.DataRecordcommentsRead, ids)
if err != nil {
return err
}
break
}
return nil | ||
} | ||
|
||
var Endpoints = map[EndpointName]Endpoint{ |
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 is a nice pattern! One small, optional suggestion: consider constructing this in an init()
function and making it private with a public getter.
It’s not a big deal, but this approach prevents unintended modifications by external clients since it wouldn’t be directly exported. I don’t think it's a major concern here, but just something to consider! Totally optional. 😊
Ex:
var endpoints map[EndpointName]Endpoint
func init() {
endpoints = map[EndpointName]Endpoint{
GetUserInfoEndpoint: {
URL: "https://api.airtable.com/v0/meta/whoami",
Method: "GET",
},
// ... existing code ...
}
}
// GetEndpoint returns the endpoint for the given name and whether it exists
func GetEndpoint(name EndpointName) (Endpoint, bool) {
endpoint, exists := endpoints[name]
return endpoint, exists
}
Description:
Airtable PAT Analyzer
Checklist:
make test-community
)?make lint
this requires golangci-lint)?