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

Centralize DN Canoicalization and minor improvements #74

Merged
merged 4 commits into from
Oct 16, 2024

Conversation

pquerna
Copy link
Contributor

@pquerna pquerna commented Oct 16, 2024

  • Centralizes DN Canoicalization into client package wrapper
  • Changes Role membership to be more like Group membership
  • Logs more details on unique group member setups

Summary by CodeRabbit

  • New Features

    • Introduced a new method for retrieving LDAP entries, enhancing the ability to fetch specific user and group information.
    • Added new filters for group member searches, improving flexibility in identifying group members.
    • Implemented a new function for canonicalizing distinguished names (DNs).
  • Bug Fixes

    • Enhanced error handling and logging for LDAP operations, providing clearer context in case of failures.
  • Refactor

    • Updated user filtering logic to improve readability and maintainability.
    • Simplified various methods to streamline processing and enhance performance.
  • Chores

    • Updated several data structures to utilize sets for improved uniqueness and efficiency in handling LDAP attributes.

Copy link

coderabbitai bot commented Oct 16, 2024

Caution

Review failed

The head commit changed during the review from a918af0 to 3542c3b.

Walkthrough

The changes in this pull request involve significant updates to several files primarily focused on integrating a new LDAP package, modifying data types, and enhancing error handling across various components. Key modifications include the replacement of LDAP-related types and methods, updates to user and group filtering logic, and the introduction of new functions for handling distinguished names (DNs). The overall structure of the code remains intact while improving clarity, reliability, and maintainability.

Changes

File Change Summary
pkg/config/config.go Integrated new LDAP package baton-ldap, updated Config struct fields from *ldap.DN to *ldap3.DN, and modified DN parsing method calls from ldap.ParseDN to ldap.CanonicalizeDN.
pkg/connector/connector_test.go Replaced ldap.ParseDN with mustParseDN for better error handling in the createConnector function.
pkg/connector/group.go Enhanced LDAP group member search logic with new filters, updated groupResource to use ldap.CanonicalizeDN, simplified member retrieval in Grants, and improved error handling with additional logging.
pkg/connector/helpers.go Changed return type of parseValues from []string to mapset.Set[string] for uniqueness, updated logic to use sets, and added import for mapset.
pkg/connector/role.go Simplified roleResource logic, improved error reporting in Grants, updated member retrieval with direct LDAP query, and enhanced error handling in Revoke.
pkg/connector/user.go Restructured user filtering logic, updated alias handling to use mapset.Set, modified logging to include canonicalized user DN, and simplified List method.
pkg/ldap/client.go Introduced LdapGet method for retrieving a single LDAP entry based on DN, filter, and attributes.
pkg/ldap/dn.go Added new file for DN canonicalization with CanonicalizeDN function to normalize DNs and handle case insensitivity.

Possibly related PRs

Suggested reviewers

  • jirwin

Poem

🐇 In the forest, changes bloom,
New LDAP paths, dispelling gloom.
With DNs parsed and members found,
Clarity in code, joy abounds!
Hops of progress, leaps of cheer,
In every line, our vision clear! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@pquerna pquerna changed the title Centralize DN Canoicalization and improve some logs Centralize DN Canoicalization and minor improvements Oct 16, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Outside diff range and nitpick comments (14)
pkg/ldap/dn.go (2)

9-26: Add a period to the comment for consistency.

The variable declaration looks good and covers common LDAP attributes efficiently. However, to address the static analysis hint and maintain consistency:

-// List of attribute types whose values should be made lowercase
+// List of attribute types whose values should be made lowercase.
🧰 Tools
🪛 GitHub Check: go-lint

[failure] 9-9:
Comment should end in a period (godot)


28-46: Consider handling empty input strings.

The CanonicalizeDN function looks well-implemented and correctly handles the canonicalization process. It properly standardizes attribute types, trims whitespace, and selectively lowercases attribute values based on the caseInsensitiveAttrs map.

Consider adding a check for empty input strings at the beginning of the function:

 func CanonicalizeDN(dn string) (*ldap3.DN, error) {
+	if dn == "" {
+		return nil, errors.New("empty DN provided")
+	}
 	dnv, err := ldap3.ParseDN(dn)
 	if err != nil {
 		return nil, err
 	}
 	// ... rest of the function
 }

This addition would provide more explicit handling for edge cases and improve the function's robustness.

pkg/connector/connector_test.go (1)

Line range hint 93-97: Well-implemented mustParseDN function

The mustParseDN function is a great addition that encapsulates DN parsing and error handling. It improves code clarity and ensures consistent error handling across tests.

Consider adding a custom error message to require.NoError to provide more context if the parsing fails:

 func mustParseDN(t *testing.T, input string) *ldap.DN {
 	dn, err := ldap.ParseDN(input)
-	require.NoError(t, err)
+	require.NoError(t, err, "Failed to parse DN: %s", input)
 	return dn
 }

This change will make it easier to identify which DN failed to parse if an error occurs.

pkg/connector/helpers.go (2)

46-46: Update function comment to reflect new return type.

The change in return type from []string to mapset.Set[string] is appropriate for the new implementation. However, the function comment should be updated to reflect this change.

Consider updating the function comment as follows:

// Parses the values of targeted attributes from an LDAP entry and returns them as a set of strings.
func parseValues(entry *ldap.Entry, targetAttrs []string) mapset.Set[string] {

47-53: LGTM: Improved implementation using a set.

The new implementation using a set is more efficient and ensures uniqueness of values. This is a good improvement over the previous slice-based approach.

Consider a minor optimization by using AddAll method of the set:

func parseValues(entry *ldap.Entry, targetAttrs []string) mapset.Set[string] {
	rv := mapset.NewSet[string]()

	for _, targetAttr := range targetAttrs {
		rv.AddAll(stringSliceToInterfaceSlice(entry.GetAttributeValues(targetAttr))...)
	}

	return rv
}

This change would reduce the number of method calls on the set and potentially improve performance for large attribute values.

pkg/config/config.go (5)

Line range hint 107-111: **Type Mismatch: Assigning ldap.DN to ldap3.DN

At line 107~, bindDN is obtained from ldap.CanonicalizeDN(bindDNValue) as a *ldap.DN, but rv.BindDN is of type *ldap3.DN. This leads to a type mismatch during assignment.

Consistently use the same DN type throughout the code by following the resolution provided earlier.


Line range hint 119-125: **Type Mismatch: Assigning ldap.DN to ldap3.DN

At line 119~, userSearchDN is assigned to rv.UserSearchDN, which is of type *ldap3.DN, but userSearchDN is a *ldap.DN. This results in a type mismatch error.

Ensure that rv.UserSearchDN and the assigned value share the same DN type as per the previous recommendations.


Line range hint 129-135: **Type Mismatch: Assigning ldap.DN to ldap3.DN

At line 129~, you're assigning groupSearchDN (of type *ldap.DN) to rv.GroupSearchDN (of type *ldap3.DN), causing a type mismatch.

Maintain consistent DN types between the variables and the struct fields as advised earlier.


Line range hint 139-145: **Type Mismatch: Assigning ldap.DN to ldap3.DN

At line 139~, roleSearchDN (type *ldap.DN) is assigned to rv.RoleSearchDN (type *ldap3.DN), leading to a type mismatch error.

Apply the same type consistency fixes as previously suggested.


8-10: Consider Renaming Import Aliases for Clarity

At lines 8~ and 10~, the packages are imported with similar aliases:

"github.com/conductorone/baton-ldap/pkg/ldap"
ldap3 "github.com/go-ldap/ldap/v3"

Using ldap and ldap3 can be confusing due to their similarity. Consider renaming the import aliases to improve code readability and maintainability.

For example:

import (
    batonldap "github.com/conductorone/baton-ldap/pkg/ldap"
    oldldap  "github.com/go-ldap/ldap/v3"
)

This makes it clearer which package is being referenced in the code, reducing potential confusion.

pkg/connector/role.go (1)

210-215: Consider logging when attribute does not exist during revoke

In the Revoke method, when the LDAP error LDAPResultNoSuchAttribute occurs, the function returns without logging. Logging this scenario can aid in understanding why the attribute was not found, especially during debugging.

Optionally, you could add a log statement to inform that the attribute did not exist:

         if lerr.ResultCode == ldap3.LDAPResultNoSuchAttribute {
+            l.Info("attribute does not exist; role membership may have already been revoked", zap.String("attribute", attrRoleMember))
             return nil, nil
         }
pkg/connector/group.go (3)

234-238: Handle cases where no user is found for a member ID

Currently, when a user is not found for a given memberId, an error is logged, and the loop continues. Consider whether this is the desired behavior or if additional handling is needed, such as notifying about missing users or cleaning up group memberships.

Ensure that the absence of a user entry for a memberId is acceptable and doesn't indicate data inconsistency.


Line range hint 293-300: Simplify extraction of username from DN

In the Grant method, when adding a user to a posixGroup, the code extracts the username from the user's DN. If the username attribute is consistently the uid, consider extracting it directly from the user's attributes instead of parsing the DN, which can be error-prone.

Modify the code to retrieve the username from the user's uid attribute:

 if slices.Contains(group.GetAttributeValues("objectClass"), "posixGroup") {
-	dn, err := ldap.CanonicalizeDN(principal.Id.Resource)
-	if err != nil {
-		return nil, err
-	}
-	username := []string{dn.RDNs[0].Attributes[0].Value}
+	userEntry, err := g.client.LdapGet(
+		ctx,
+		ldap.CanonicalizeDN(principal.Id.Resource),
+		userFilter,
+		[]string{"uid"},
+	)
+	if err != nil {
+		return nil, err
+	}
+	username := userEntry.GetAttributeValues("uid")
 	modifyRequest.Add(attrGroupMemberPosix, username)
 } else {

Line range hint 335-342: Simplify username extraction during revocation

Similarly, in the Revoke method, when removing a user from a posixGroup, consider retrieving the username from the user's attributes instead of parsing the DN.

Update the code to fetch the uid attribute:

 if slices.Contains(group.GetAttributeValues("objectClass"), "posixGroup") {
-	dn, err := ldap.CanonicalizeDN(principal.Id.Resource)
-	if err != nil {
-		return nil, err
-	}
-	username := []string{dn.RDNs[0].Attributes[0].Value}
+	userEntry, err := g.client.LdapGet(
+		ctx,
+		ldap.CanonicalizeDN(principal.Id.Resource),
+		userFilter,
+		[]string{"uid"},
+	)
+	if err != nil {
+		return nil, err
+	}
+	username := userEntry.GetAttributeValues("uid")
 	modifyRequest.Delete(attrGroupMemberPosix, username)
 } else {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6f04b80 and 4753d5a.

📒 Files selected for processing (8)
  • pkg/config/config.go (6 hunks)
  • pkg/connector/connector_test.go (1 hunks)
  • pkg/connector/group.go (9 hunks)
  • pkg/connector/helpers.go (2 hunks)
  • pkg/connector/role.go (4 hunks)
  • pkg/connector/user.go (6 hunks)
  • pkg/ldap/client.go (1 hunks)
  • pkg/ldap/dn.go (1 hunks)
🧰 Additional context used
🪛 GitHub Check: go-lint
pkg/connector/group.go

[failure] 27-27:
var-naming: const groupMemberUidFilter should be groupMemberUIDFilter (revive)


[failure] 169-169:
var-naming: var numericUidRe should be numericUIDRe (revive)

pkg/ldap/dn.go

[failure] 9-9:
Comment should end in a period (godot)

🔇 Additional comments (17)
pkg/ldap/dn.go (1)

1-46: Well-structured implementation of LDAP DN canonicalization.

This new file introduces a robust implementation for canonicalizing LDAP Distinguished Names. The CanonicalizeDN function effectively standardizes the case of attributes and values, which is crucial for consistent LDAP operations. The use of a pre-defined map for case-insensitive attributes is an efficient approach.

The code is well-organized and follows good practices. With the minor suggestions implemented, this file will provide a solid foundation for LDAP-related operations in the project.

🧰 Tools
🪛 GitHub Check: go-lint

[failure] 9-9:
Comment should end in a period (godot)

pkg/connector/connector_test.go (2)

86-89: Excellent refactoring of DN parsing!

The replacement of ldap.ParseDN calls with mustParseDN is a great improvement. This change:

  1. Centralizes DN parsing logic, aligning with the PR objective.
  2. Simplifies error handling in the createConnector function.
  3. Improves code readability and reduces duplication.
  4. Ensures that any DN parsing errors will cause the test to fail immediately, improving test reliability.

Line range hint 1-97: Summary: Excellent improvements to DN handling

The changes in this file successfully centralize DN canonicalization, as stated in the PR objectives. The introduction of the mustParseDN function and its usage in createConnector improve code readability, maintainability, and error handling. These changes will make the tests more robust and easier to understand.

The modifications align well with best practices for test code organization and error handling in Go. Great job on this refactoring!

pkg/connector/helpers.go (1)

9-9: LGTM: New import for mapset package.

The addition of the mapset import is appropriate for the changes made to the parseValues function. Using an alias and specifying the version is a good practice.

pkg/ldap/client.go (1)

Line range hint 1-138: Overall assessment of the changes

The addition of the LdapGet method to the Client struct is a valuable improvement to the LDAP client implementation. It provides a convenient way to retrieve a single LDAP entry, which is a common operation in LDAP-based systems.

The method is well-integrated with the existing LdapSearch method and follows the error handling patterns established in the rest of the file. With the suggested improvements to error handling and documentation, this change will enhance the usability and maintainability of the LDAP client.

The rest of the file remains unchanged, maintaining the existing functionality and structure of the LDAP client.

pkg/connector/user.go (6)

13-13: LGTM: Import statement for mapset added.

The addition of the mapset import is appropriate for the changes made in the parseUserLogin function. This package provides efficient set operations, which will improve the handling of user aliases.


25-26: LGTM: Constants refactored for better readability and maintainability.

The introduction of the userObjectClasses constant and its use in userFilter is a good refactoring. It improves code readability and makes future modifications to the object classes easier to manage.


Line range hint 98-118: LGTM: Improved efficiency in parseUserLogin function.

The refactoring of parseUserLogin function is well done:

  1. Using mapset.Set instead of a slice improves the efficiency of managing unique aliases.
  2. The new implementation correctly ensures that the login is not included in the returned aliases.
  3. The changes enhance both the performance and correctness of the function.

These modifications align well with best practices for handling sets of unique values.


222-227: LGTM: Improved DN handling in userResource function.

The changes to the userResource function enhance the handling of Distinguished Names (DNs):

  1. The use of ldap.CanonicalizeDN ensures consistent DN formatting.
  2. Logging the canonicalized DN improves traceability and debugging.
  3. Using the canonicalized DN in resource creation maintains consistency across the system.
  4. Proper error handling for the canonicalization process is in place.

These modifications improve the robustness and consistency of DN handling in the connector.

Also applies to: 232-232


275-275: LGTM: Simplified List method in userResourceType.

The modification in the List method, where userEntry is directly passed to userResource function, is a good optimization:

  1. It simplifies the code by removing unnecessary variable assignment.
  2. This change may slightly improve performance by avoiding redundant object creation.

This is a good example of clean and efficient coding practices.


Line range hint 1-305: Overall assessment: Excellent improvements to code quality and consistency.

The changes in this file align well with the PR objectives of centralizing DN canonicalization and improving logs. Key improvements include:

  1. Enhanced efficiency in handling user aliases using mapset.Set.
  2. Consistent canonicalization of Distinguished Names (DNs).
  3. Improved logging for better traceability and debugging.
  4. Code simplification and potential performance improvements in the List method.
  5. Better constant management for user object classes and filters.

These modifications collectively enhance the code's readability, maintainability, and efficiency without introducing any major architectural changes or new features. The changes are well-integrated and consistent throughout the file, demonstrating a thoughtful approach to code improvement.

pkg/connector/role.go (5)

5-5: Approved: Added import for the errors package

The addition of the errors package import is appropriate for improved error handling.


15-16: Approved: Added imports for enhanced logging

The imports of ctxzap and zap packages enhance logging capabilities and are correctly implemented.


123-128: Approved: Proper logger extraction and role DN canonicalization

The extraction of the logger from context and the canonicalization of roleDN are correctly implemented. Adding role_dn to the logger's context will aid in debugging.


130-135: Approved: Retrieval of LDAP role object

Using r.client.LdapGet to retrieve the LDAP role object with the specified parameters is appropriate and correctly implemented.


142-145: Approved: Parsing role members and iterating over them

The parsing of role members using parseValues and the iteration over members.Iter() are correctly implemented to retrieve and process each member DN.

pkg/connector/group.go (1)

215-218: Ensure proper handling of member ID filters

When constructing LDAP filters for member IDs, make sure that the attributes used (uid and cn) match the directory schema. If the directory uses different attributes for user identification, the filters may not return the correct entries.

Consider verifying that the attributes uid and cn are appropriate for your LDAP schema.

Run the following script to check available user attributes:

Comment on lines +122 to +138
func (c *Client) LdapGet(ctx context.Context,
searchDN *ldap.DN,
filter string,
attrNames []string,
) (*ldap.Entry, error) {
entries, _, err := c.LdapSearch(ctx, ldap.ScopeBaseObject, searchDN, filter, attrNames, "", 1)
if err != nil {
return nil, err
}
if len(entries) == 0 {
return nil, fmt.Errorf("entry not found: %s", searchDN.String())
}
if len(entries) > 1 {
return nil, fmt.Errorf("multiple entries found: %s", searchDN.String())
}
return entries[0], nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance the LdapGet method with comments and improved error handling.

The implementation of LdapGet is solid, but we can make a few improvements:

  1. Add a comment explaining the purpose of the method.
  2. Improve error messages to be more specific.
  3. Consider using custom error types for "not found" and "multiple entries" cases.

Here's a suggested improvement:

+// LdapGet retrieves a single LDAP entry based on the provided distinguished name (DN),
+// filter, and attribute names. It returns an error if no entry or multiple entries are found.
 func (c *Client) LdapGet(ctx context.Context,
 	searchDN *ldap.DN,
 	filter string,
 	attrNames []string,
 ) (*ldap.Entry, error) {
 	entries, _, err := c.LdapSearch(ctx, ldap.ScopeBaseObject, searchDN, filter, attrNames, "", 1)
 	if err != nil {
 		return nil, err
 	}
 	if len(entries) == 0 {
-		return nil, fmt.Errorf("entry not found: %s", searchDN.String())
+		return nil, fmt.Errorf("ldap entry not found for DN: %s, filter: %s", searchDN.String(), filter)
 	}
 	if len(entries) > 1 {
-		return nil, fmt.Errorf("multiple entries found: %s", searchDN.String())
+		return nil, fmt.Errorf("multiple ldap entries found for DN: %s, filter: %s", searchDN.String(), filter)
 	}
 	return entries[0], nil
 }

Consider defining custom error types for more granular error handling:

var (
    ErrLdapEntryNotFound = errors.New("ldap entry not found")
    ErrMultipleLdapEntriesFound = errors.New("multiple ldap entries found")
)

Then use these custom errors in the LdapGet method:

if len(entries) == 0 {
    return nil, fmt.Errorf("%w: DN: %s, filter: %s", ErrLdapEntryNotFound, searchDN.String(), filter)
}
if len(entries) > 1 {
    return nil, fmt.Errorf("%w: DN: %s, filter: %s", ErrMultipleLdapEntriesFound, searchDN.String(), filter)
}

This approach allows callers to use errors.Is for more precise error handling.

@pquerna pquerna force-pushed the pq/improved_dns_and_logs branch from e44260a to a918af0 Compare October 16, 2024 04:54
@pquerna pquerna merged commit fde7fee into main Oct 16, 2024
3 checks passed
@ggreer ggreer deleted the pq/improved_dns_and_logs branch October 16, 2024 15:20
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.

1 participant