-
Notifications
You must be signed in to change notification settings - Fork 151
feat: Add SPNEGO Authentication Middleware #1368
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
base: main
Are you sure you want to change the base?
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a new SPNEGO (Kerberos) authentication middleware for Fiber v3, including configuration and keytab lookup utilities, identity context helpers, keytab introspection and mock builders, documentation, example usage, and comprehensive tests. No existing exported declarations were modified. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant FiberMW as SPNEGO Middleware
participant Keytab as KeytabLookup (Config)
participant SPNEGO as gokrb5 SPNEGO Handler
participant Handler as Next Handler
participant Ctx as Fiber Context
Client->>FiberMW: HTTP Request
FiberMW->>Keytab: cfg.KeytabLookup()
Keytab-->>FiberMW: *keytab.Keytab or error
alt Keytab error
FiberMW-->>Client: 500 Internal Server Error
else keytab returned
FiberMW->>SPNEGO: adapt request -> ServeHTTP(...)
SPNEGO-->>FiberMW: success or auth failure
alt Auth success
FiberMW->>Ctx: SetAuthenticatedIdentityToContext(identity)
FiberMW->>Handler: call next
Handler->>Ctx: GetAuthenticatedIdentityFromContext()
Handler-->>Client: 200 OK + response
else Auth failure
FiberMW-->>Client: 401 Unauthorized (WWW-Authenticate: Negotiate)
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45–60 minutes
Possibly related issues
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
🔇 Additional comments (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Summary of Changes
Hello @jarod2011, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
I'm introducing a new SPNEGO authentication middleware for Fiber, which provides Kerberos-based single sign-on (SSO) capabilities. This middleware is designed to be enterprise-grade, supporting dynamic keytab loading and adhering to relevant RFCs, making it compatible with both Fiber v2 and v3.
Highlights
- SPNEGO Authentication Middleware: I've implemented a new SPNEGO authentication middleware for Fiber applications.
- Fiber Version Compatibility: The middleware supports both Fiber v2 and Fiber v3 versions, ensuring broad compatibility.
- Kerberos-based Single Sign-On (SSO): This feature enables Kerberos-based Single Sign-On (SSO) capabilities for Fiber applications.
- Dynamic Keytab Loading: A flexible keytab lookup system has been designed, allowing dynamic retrieval of keytabs from various sources like files, databases, or remote services.
- RFC Compliance: The implementation ensures full compliance with relevant RFCs, specifically RFC 4559, RFC 4120, and RFC 7235, for robust and standard-compliant authentication.
- Comprehensive Test Coverage: The new middleware comes with comprehensive test coverage, exceeding 90%, to ensure reliability and proper handling of edge cases and errors.
- Detailed Documentation: Complete documentation, including usage examples for both Fiber v2 and v3, has been provided in the README to facilitate easy adoption.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
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.
Code Review
This pull request introduces a new SPNEGO authentication middleware for Fiber, with support for both v2 and v3. The implementation is well-structured, separating configuration from the version-specific middleware logic. However, there are several critical issues that need to be addressed. The response writer wrapper (wrapCtx) in both v2 and v3 implementations is flawed, which will prevent the SPNEGO authentication challenge from working correctly. Additionally, there are uncompilable helper functions in the test files, and the provided runnable example will panic due to missing files. The documentation in the README also contains incorrect code snippets. Addressing these issues will significantly improve the quality and usability of this new middleware.
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.
Actionable comments posted: 16
🧹 Nitpick comments (4)
spnego/example.go (2)
36-46: Consider graceful shutdown approach.Using
time.Afterfor server shutdown timing is not ideal for production examples, even though this is acceptable for demonstration purposes.Consider showing a more realistic shutdown pattern:
log.Info("Server is running on :3000") - go func() { - <-time.After(time.Second * 1) - fmt.Println("use curl -kv --negotiate http://sso.example.local:3000/protected/resource") - fmt.Println("if response is 401, execute `klist` to check use kerberos session") - <-time.After(time.Second * 2) - fmt.Println("close server") - if err = app.Shutdown(); err != nil { - panic(fmt.Errorf("shutdown server failed: %w", err)) - } - }() + fmt.Println("use curl -kv --negotiate http://sso.example.local:3000/protected/resource") + fmt.Println("if response is 401, execute `klist` to check use kerberos session")
47-49: Verify server binding address.The example binds to
sso.example.local:3000which requires DNS resolution. Consider usinglocalhost:3000or0.0.0.0:3000for broader compatibility in examples.- if err := app.Listen("sso.example.local:3000"); err != nil { + if err := app.Listen(":3000"); err != nil {spnego/v2/spnego.go (1)
34-36: Consider using a more robust logger initializationThe comment indicates difficulty obtaining Fiber's internal logger. Consider providing a configuration option for users to pass their preferred logger instance.
This would allow users to maintain consistent logging across their application rather than having a separate logger instance for the middleware.
spnego/v3/spnego.go (1)
1-104: Consider extracting common SPNEGO logicBoth v2 and v3 implementations share significant logic. While API differences between Fiber versions require separate implementations, common patterns could be extracted.
Consider creating a shared internal package for:
- Common error wrapping patterns
- Identity management utilities
- SPNEGO handler creation logic
This would reduce code duplication and make maintenance easier while still allowing version-specific adaptations.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
spnego/go.modis excluded by!**/*.modspnego/go.sumis excluded by!**/*.sum,!**/*.sum
📒 Files selected for processing (9)
spnego/README.md(1 hunks)spnego/README.zh-CN.md(1 hunks)spnego/config/config.go(1 hunks)spnego/doc.go(1 hunks)spnego/example.go(1 hunks)spnego/v2/spnego.go(1 hunks)spnego/v2/spnego_test.go(1 hunks)spnego/v3/spnego.go(1 hunks)spnego/v3/spnego_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (15)
📓 Common learnings
Learnt from: ReneWerner87
PR: gofiber/recipes#0
File: :0-0
Timestamp: 2024-11-26T20:05:15.793Z
Learning: For future contributions to the `gofiber/recipes` repository, ensure that the tasks outlined in `.github/CONTRIBUTING.md` are incorporated, including creating a new directory without a "fiber" prefix, adding a `README.md` with Docusaurus metadata, and updating the overview by running `make generate`.
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-10-16T10:04:06.328Z
Learning: The i18n functionality in the gofiber/contrib repository is being refactored from middleware to a global container to improve robustness and performance. The global container will be initialized once before setting up routes and will manage the i18n bundle and localizer map.
Learnt from: ReneWerner87
PR: gofiber/fiber#0
File: :0-0
Timestamp: 2024-12-01T10:28:36.011Z
Learning: Feature request #3224 has been created to add support for square bracket notation and comma-separated values in multipart form data in Fiber, while maintaining binary data transfer capabilities. This would bring parity with the existing form-urlencoded functionality.
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-07-03T11:59:00.303Z
Learning: The i18n functionality in the gofiber/contrib repository is being refactored from middleware to a global container to improve robustness and performance. The global container will be initialized once before setting up routes and will manage the i18n bundle and localizer map.
📚 Learning: in the `clean-code` example at `clean-code/app/server/server.go` using the go fiber framework, it's ...
Learnt from: norri
PR: gofiber/recipes#2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.
Applied to files:
spnego/doc.gospnego/example.gospnego/README.md
📚 Learning: the storage package in gofiber/storage repository can be used independently of the fiber web framewo...
Learnt from: ReneWerner87
PR: gofiber/storage#0
File: :0-0
Timestamp: 2025-02-12T11:24:31.153Z
Learning: The storage package in gofiber/storage repository can be used independently of the Fiber web framework.
Applied to files:
spnego/doc.go
📚 Learning: for future contributions to the `gofiber/recipes` repository, ensure that the tasks outlined in `.gi...
Learnt from: ReneWerner87
PR: gofiber/recipes#0
File: :0-0
Timestamp: 2024-11-26T20:05:15.793Z
Learning: For future contributions to the `gofiber/recipes` repository, ensure that the tasks outlined in `.github/CONTRIBUTING.md` are incorporated, including creating a new directory without a "fiber" prefix, adding a `README.md` with Docusaurus metadata, and updating the overview by running `make generate`.
Applied to files:
spnego/README.zh-CN.mdspnego/README.md
📚 Learning: the i18n functionality in the gofiber/contrib repository is being refactored from middleware to a gl...
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-10-16T10:04:06.328Z
Learning: The i18n functionality in the gofiber/contrib repository is being refactored from middleware to a global container to improve robustness and performance. The global container will be initialized once before setting up routes and will manage the i18n bundle and localizer map.
Applied to files:
spnego/README.zh-CN.md
📚 Learning: the `encryptcookie_test.go` file contains unit tests that validate key lengths for both `encryptcook...
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-07-02T13:29:56.992Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.
Applied to files:
spnego/v3/spnego_test.gospnego/v2/spnego_test.go
📚 Learning: parallel tests for `session.save` already exist in the `middleware/session/session_test.go` file, sp...
Learnt from: sixcolors
PR: gofiber/fiber#3051
File: middleware/session/session.go:215-216
Timestamp: 2024-06-30T00:38:06.580Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.
Applied to files:
spnego/v3/spnego_test.gospnego/v2/spnego_test.go
📚 Learning: in go test files, prefer using the `require` methods from the `testify` package for assertions inste...
Learnt from: efectn
PR: gofiber/fiber#3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.
Applied to files:
spnego/v3/spnego_test.gospnego/v2/spnego_test.go
📚 Learning: in the `test_session_next` function in `middleware/session/middleware_test.go`, the variable `donext...
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/session/middleware_test.go:400-407
Timestamp: 2024-09-25T17:09:03.756Z
Learning: In the `Test_Session_Next` function in `middleware/session/middleware_test.go`, the variable `doNext` is properly synchronized with the `muNext` mutex for both read and write access.
Applied to files:
spnego/v3/spnego_test.gospnego/v2/spnego_test.go
📚 Learning: unit tests for key length enforcement in both `encryptcookie` and `decryptcookie` functions have bee...
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:20-23
Timestamp: 2024-07-01T03:44:03.672Z
Learning: Unit tests for key length enforcement in both `EncryptCookie` and `DecryptCookie` functions have been added to ensure robust validation and prevent potential runtime errors.
Applied to files:
spnego/v3/spnego_test.gospnego/v2/spnego_test.go
📚 Learning: the project uses the testify/assert package for assertions in unit tests....
Learnt from: sixcolors
PR: gofiber/fiber#2922
File: middleware/cors/utils.go:63-71
Timestamp: 2024-07-26T21:00:12.902Z
Learning: The project uses the testify/assert package for assertions in unit tests.
Applied to files:
spnego/v3/spnego_test.gospnego/v2/spnego_test.go
📚 Learning: unit tests in this project use testify require....
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests in this project use testify require.
Applied to files:
spnego/v3/spnego_test.gospnego/v2/spnego_test.go
📚 Learning: in the fiber framework tests, using `ctx.response.header.cookie` may not be suitable for parsing coo...
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-09-25T15:57:10.221Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.
Applied to files:
spnego/v3/spnego_test.gospnego/v2/spnego_test.go
📚 Learning: in the fiber framework, breaking changes are acceptable when moving from version 2 to version 3, inc...
Learnt from: ReneWerner87
PR: gofiber/fiber#3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.
Applied to files:
spnego/README.mdspnego/v3/spnego.gospnego/v2/spnego.go
📚 Learning: in the `middleware/adaptor/adaptor.go` file of the fiber framework, when updating context handling, ...
Learnt from: gaby
PR: gofiber/fiber#3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.
Applied to files:
spnego/v3/spnego.gospnego/v2/spnego.go
🧬 Code Graph Analysis (1)
spnego/v3/spnego.go (2)
spnego/v2/spnego.go (2)
NewSpnegoKrb5AuthenticateMiddleware(25-62)GetAuthenticatedIdentityFromContext(81-84)spnego/config/config.go (5)
Config(37-42)ErrConfigInvalidOfKeytabLookupFunctionRequired(12-12)ErrLookupKeytabFailed(15-15)ErrConvertRequestFailed(18-18)ContextKeyOfIdentity(27-27)
🔇 Additional comments (5)
spnego/config/config.go (2)
11-24: LGTM! Well-defined error variables.The error variables are clearly named and provide specific context for different failure scenarios. The grouping of related errors using
var()block is appropriate.
29-33: Excellent design for extensibility.The
KeytabLookupFunctype provides a clean abstraction that enables dynamic keytab retrieval from various sources. The documentation clearly explains the extensibility benefits.spnego/doc.go (1)
7-51: LGTM! Comprehensive package documentation.The documentation provides excellent coverage of version compatibility, usage examples, and API overview. The example code demonstrates proper error handling and middleware usage patterns.
spnego/v2/spnego_test.go (1)
145-162: LGTM!The test properly validates the context-based identity storage and retrieval functionality.
spnego/v2/spnego.go (1)
97-99: Verify response header retrieval in Header() implementationThe
http.ResponseWriter.Header()method must return response headers, but the current code returns request headers viaw.Ctx.GetReqHeaders().
- Confirm whether Fiber v2’s
Ctxinterface offers a response-header method (for example,Response().Header()).- If it does, replace
GetReqHeaders()with the correct call so thatHeader()returns the response headers as required byhttp.ResponseWriter.
|
@jarod2011 |
Move configuration and utility functions from config package to main package, optimize code organization Add new error types and utility functions, improve test coverage Simplify documentation content, remove redundant information
- Update English README document structure, add badges and document ID - Synchronize update of Chinese translation version - Unify keytab lookup implementation in example code
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.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
spnego/README.zh-CN.md (1)
190-191: Fix stray fenced code block (markdownlint MD040).A dangling triple/quadruple backtick breaks rendering and linting. Remove it or provide a language.
-``` - -```` +<!-- removed stray fences -->
♻️ Duplicate comments (8)
spnego/doc.go (1)
1-3: Duplicate doc line previously flagged appears resolved.The earlier duplicate line reported in past reviews is no longer present. Good cleanup.
spnego/README.md (3)
44-91: Fix v3 example: incorrect API/package usage and unused imports.Examples won’t compile as-is. Past reviews noted the same; apply consistent
configusage and clean imports.import ( - "fmt" - "time" - - "github.com/gofiber/contrib/spnego" - "github.com/gofiber/contrib/spnego/utils" - v3 "github.com/gofiber/contrib/spnego/v3" - "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/log" + "fmt" + "log" + + "github.com/gofiber/fiber/v3" + "github.com/gofiber/contrib/spnego" + "github.com/gofiber/contrib/spnego/config" + v3 "github.com/gofiber/contrib/spnego/v3" ) @@ - // For testing, you can create a mock keytab file using utils.NewMockKeytab - // In production, use a real keytab file - keytabLookup, err := spnego.NewKeytabFileLookupFunc("/path/to/keytab/file.keytab") + // Use a keytab file (replace with your path). For tests, see utils.NewMockKeytab. + keytabLookup, err := config.NewKeytabFileLookupFunc("/path/to/keytab/file.keytab") @@ - authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{ - KeytabLookup: keytabLookup, - }) + authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(config.Config{ + KeytabLookup: keytabLookup, + })Note: If the repository adopts the
github.com/gofiber/contrib/v3/spnegolayout, update the three imports accordingly:contrib/v3/spnego,contrib/v3/spnego/config, and keepfiber/v3.
95-144: Fix v2 example: same API/package issues as v3 snippet.Mirror the corrections for v2 and ensure
logandosare actually used.import ( "fmt" "log" "os" - "github.com/gofiber/contrib/spnego" - "github.com/gofiber/contrib/spnego/utils" - v2 "github.com/gofiber/contrib/spnego/v2" "github.com/gofiber/fiber/v2" + "github.com/gofiber/contrib/spnego" + "github.com/gofiber/contrib/spnego/config" + v2 "github.com/gofiber/contrib/spnego/v2" ) @@ - // For testing, you can create a mock keytab file using utils.NewMockKeytab - // In production, use a real keytab file - keytabLookup, err := spnego.NewKeytabFileLookupFunc("/path/to/keytab/file.keytab") + // Use a keytab file (replace with your path). For tests, see utils.NewMockKeytab. + keytabLookup, err := config.NewKeytabFileLookupFunc("/path/to/keytab/file.keytab") @@ - authMiddleware, err := v2.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{ + authMiddleware, err := v2.NewSpnegoKrb5AuthenticateMiddleware(config.Config{ KeytabLookup: keytabLookup, // Optional: Set a custom logger Log: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), })
166-179: API reference: pointNewKeytabFileLookupFuncto the config package and correct return types.Reference belongs to the
configpackage and returnsconfig.KeytabLookupFunc.-### `NewKeytabFileLookupFunc(keytabFiles ...string) (KeytabLookupFunc, error)` +### `config.NewKeytabFileLookupFunc(keytabFiles ...string) (config.KeytabLookupFunc, error)` -Creates a new KeytabLookupFunc that loads keytab files. +Creates a new KeytabLookupFunc that loads and merges keytab files.spnego/README.zh-CN.md (1)
189-189: Don’t reference an unexported/internal context key in docs.The variable
spnego.contextKeyOfIdentityis internal and not part of the public API. Directing users to it is misleading; the supported API isspnego.GetAuthenticatedIdentityFromContext(andSetAuthenticatedIdentityToContext).-- 已认证的身份使用`spnego.contextKeyOfIdentity`存储在Fiber上下文中 +- 已认证的身份通过 `spnego.GetAuthenticatedIdentityFromContext(ctx)` 从 Fiber 上下文中获取。 + 中间件会在认证成功时将身份放入上下文;请使用公开的辅助函数进行读写。spnego/v3/spnego.go (2)
55-57: Verify ResponseWriter adapter writes response headers, not request headers.The SPNEGO library sets WWW-Authenticate on the ResponseWriter. If utils.NewWrapFiberContext returns a Header() backed by request headers, the challenge will never reach clients. Ensure Header() maps to ctx.Response().Header. Add/adjust tests (see v3 test suggestion) to catch this.
#!/bin/bash # Verify Header() implementation of the WrapFiberContext adapter rg -nP -C3 'type\s+WrapFiberContext.*\n|\bHeader\(\)\s+http\.Header' spnego/utils | sed -n '1,200p' rg -nP -C3 'GetReqHeaders|Response\(\)\.Header' spnego/utilsExpected: Header() should read/write via response headers (ctx.Response().Header); no use of GetReqHeaders for ResponseWriter.
--- `32-34`: **Avoid unsafe type assertion when defaulting the logger.** Directly asserting to *log.Logger can panic if Fiber’s default logger changes type. Use comma-ok and a sane fallback. ```diff - if cfg.Log == nil { - cfg.Log = flog.DefaultLogger().Logger().(*log.Logger) - } + if cfg.Log == nil { + if logger, ok := flog.DefaultLogger().Logger().(*log.Logger); ok { + cfg.Log = logger + } else { + cfg.Log = log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds) + } + }Also add missing import if needed:
import ( "fmt" "log" + "os" "net/http"spnego/v2/spnego.go (1)
56-58: Same ResponseWriter adapter concern for v2.Confirm that utils.NewWrapFiberContext(*fiber.Ctx) implements http.ResponseWriter over response headers and status, not request headers—otherwise WWW-Authenticate/Negotiate won’t be emitted.
#!/bin/bash # Check the v2 adapter as well (shared utils) rg -nP -C3 'WrapFiberContext.*Header\(\)\s+http\.Header' spnego/utils rg -nP -C3 'GetReqHeaders|Response\(\)\.Header' spnego/utils
🧹 Nitpick comments (28)
spnego/doc.go (1)
1-4: Tighten package doc; make it a single, punctuated sentence.The first sentence should fully describe the package and end with a period. Suggest collapsing the two lines into one sentence for godoc clarity.
-// Package spnego provides SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) -// authentication middleware for Fiber applications. -// It enables Kerberos authentication for HTTP requests. +// Package spnego provides SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication middleware for Fiber applications, enabling Kerberos-based SSO for HTTP requests.spnego/error.go (2)
5-18: Polish error message phrasing for clarity/consistency.Minor grammar tweaks improve readability and align with Go error style (“failed to …”, “invalid config: …”).
-var ErrConfigInvalidOfKeytabLookupFunctionRequired = errors.New("config invalid: keytab lookup function is required") +var ErrConfigInvalidOfKeytabLookupFunctionRequired = errors.New("invalid config: keytab lookup function is required") -var ErrLookupKeytabFailed = errors.New("keytab lookup failed") +var ErrLookupKeytabFailed = errors.New("failed to look up keytab") -var ErrConvertRequestFailed = errors.New("convert request failed") +var ErrConvertRequestFailed = errors.New("failed to convert request") -var ErrConfigInvalidOfAtLeastOneKeytabFileRequired = errors.New("config invalid: at least one keytab file required") +var ErrConfigInvalidOfAtLeastOneKeytabFileRequired = errors.New("invalid config: at least one keytab file is required") -var ErrLoadKeytabFileFailed = errors.New("load keytab failed") +var ErrLoadKeytabFileFailed = errors.New("failed to load keytab")
5-18: Consolidate or Rename Duplicated Error Variables Across PackagesI’ve confirmed that the same error variables are declared both in
spnego/error.go(lines 6, 9, 12, 15, 18) and inspnego/config/config.go(lines 12, 15, 18, 20). Since they live in separate packages (spnegovs.spnego/config), you won’t get a compile-time conflict, but this duplication can lead to maintenance overhead and subtle inconsistencies.• spnego/error.go:
- ErrConfigInvalidOfKeytabLookupFunctionRequired
- ErrLookupKeytabFailed
- ErrConvertRequestFailed
- ErrConfigInvalidOfAtLeastOneKeytabFileRequired
- ErrLoadKeytabFileFailed
• spnego/config/config.go:
- ErrConfigInvalidOfKeytabLookupFunctionRequired
- ErrLookupKeytabFailed
- ErrConvertRequestFailed
- ErrConfigInvalidOfAtLeastOneKeytabFileRequired
Consider one of the following to reduce duplication and keep error handling consistent:
- Extract these shared error declarations into a common package (e.g.,
spnego/errors) and import them where needed.- Rename or differentiate the config-specific errors if they’re semantically distinct.
- Document the duplication clearly if keeping separate definitions is intentional.
spnego/identity.go (1)
5-7: Interface name duplicates utils.FiberContext; consider rename to avoid confusion.There’s another
FiberContextin spnego/utils. Different packages, but same name increases cognitive load. Optional rename, e.g.,FiberLocalsContext.spnego/README.md (4)
168-174: API signature for middleware should mention versioned package explicitly or show both.Currently ambiguous. Consider adding both v2 and v3 signatures or a note.
-### `NewSpnegoKrb5AuthenticateMiddleware(cfg spnego.Config) (fiber.Handler, error)` +### `v3.NewSpnegoKrb5AuthenticateMiddleware(cfg config.Config) (fiber.Handler, error)` +### `v2.NewSpnegoKrb5AuthenticateMiddleware(cfg config.Config) (fiber.Handler, error)`
198-199: Avoid documenting an unexported key; describe behavior instead.Mentioning
spnego.contextKeyOfIdentityleaks an internal detail and may change. Prefer describing the helper functions.-- Authenticated identities are stored in the Fiber context using `spnego.contextKeyOfIdentity` +- Authenticated identities are stored in the Fiber context via `spnego.SetAuthenticatedIdentityToContext` and can be retrieved with `spnego.GetAuthenticatedIdentityFromContext`.
70-73: Replace hard tab with spaces (markdownlint MD010).There’s a hard tab before
KeytabLookup:; use spaces.- authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{ - KeytabLookup: keytabLookup, - }) + authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{ + KeytabLookup: keytabLookup, + })
5-11: Add standard header block and link from root README.Per maintainer comment, adopt the contrib README header (badges, quick links) and add an entry in the repository root README pointing to this middleware.
I can open a follow-up PR to update the root README and add the standardized header here if you’d like.
spnego/utils/adapter.go (2)
49-57: Initialize adapter state in constructor.Initialize
headerand default status.func NewWrapFiberContext[T FiberContext[T]](ctx T) *WrapFiberContextAdaptHttpResponseWriter[T] { - return &WrapFiberContextAdaptHttpResponseWriter[T]{ - ctx: ctx, - } + return &WrapFiberContextAdaptHttpResponseWriter[T]{ + ctx: ctx, + header: make(http.Header), + statusCode: http.StatusOK, + } }
21-26: Name is overly verbose; consider a shorter, conventional type name.E.g.,
ResponseWriterAdapter[T]orFiberResponseWriter[T]. Improves readability without changing behavior.spnego/utils/adapter_test.go (2)
19-19: Fix test name typo: “Fiver” → “Fiber”.Keeps naming consistent and discoverable when running tests selectively.
-func TestNewWrapFiberContextOfFiverV2(t *testing.T) { +func TestNewWrapFiberContextOfFiberV2(t *testing.T) { @@ -func TestNewWrapFiberContextOfFiverV3(t *testing.T) { +func TestNewWrapFiberContextOfFiberV3(t *testing.T) {Also applies to: 41-41
19-33: Make tests parallel.Both tests are independent; running them in parallel speeds up the suite without side effects.
func TestNewWrapFiberContextOfFiberV2(t *testing.T) { + t.Parallel() @@ func TestNewWrapFiberContextOfFiberV3(t *testing.T) { + t.Parallel()Also applies to: 41-55
spnego/README.zh-CN.md (2)
22-31: Optional: add shell prompt language to installation block for better formatting.Improves readability and satisfies markdown linters in some setups.
-```bash +```bash # 对于Fiber v3 go get github.com/gofiber/contrib/spnego/v3 @@ # 对于Fiber v2 go get github.com/gofiber/contrib/spnego/v2--- `1-21`: **Consider adding a pointer to the repository root README as requested by reviewers.** A short “See root README for overview” link addresses the request from ReneWerner87 and helps discoverability. Would you like me to draft a concise entry (EN/zh-CN) for the root README that links to this guide and highlights SPNEGO support? </blockquote></details> <details> <summary>spnego/utils/keytab_test.go (1)</summary><blockquote> `10-46`: **Make the test parallel; otherwise looks solid.** Parallelizing is safe here and reduces suite time. The assertions are deterministic (Unix timestamps). ```diff func TestGetKeytabInfo(t *testing.T) { + t.Parallel() tm := time.Now()spnego/config_test.go (4)
17-27: Rename subtest and clarify its intent (invalid file vs. not found).The case writes an invalid keytab file, so "not found" is misleading. Rename to "invalid keytab file content" for clarity.
- t.Run("test not found keytab file", func(t *testing.T) { + t.Run("test invalid keytab file content", func(t *testing.T) {
18-22: Use t.TempDir to avoid filename collisions and manual cleanup.Writing to ./invalid.keytab can collide across runs and requires manual cleanup. Prefer t.TempDir + filepath.Join to isolate files.
- err := os.WriteFile("./invalid.keytab", []byte("12345"), 0600) + dir := t.TempDir() + invalid := filepath.Join(dir, "invalid.keytab") + err := os.WriteFile(invalid, []byte("12345"), 0o600) ... - fn, err := NewKeytabFileLookupFunc("./invalid.keytab") + fn, err := NewKeytabFileLookupFunc(invalid)Also add:
import ( "os" + "path/filepath" "testing" "time"Also applies to: 23-27
75-79: Multiple-files test: prefer temp paths; avoid hardcoded names and manual cleanup.Same rationale as above—use t.TempDir and filepath.Join; drop os.Remove via t.Cleanup.
- err = os.WriteFile("./invalid1.keytab", []byte("12345"), 0600) + dir := t.TempDir() + invalid := filepath.Join(dir, "invalid1.keytab") + err = os.WriteFile(invalid, []byte("12345"), 0o600) ... - fn, err := NewKeytabFileLookupFunc("./temp.keytab", "./invalid1.keytab") + fn, err := NewKeytabFileLookupFunc(filepath.Join(dir, "temp.keytab"), invalid)
81-131: Assert by content rather than relying on order, and use temp files.GetKeytabInfo sorts by principal; asserting indices is okay but brittle if sort criteria change. Prefer verifying via a map keyed by principal with per-pair checks. Also use t.TempDir for both files as suggested above.
- require.Equal(t, info[0].PrincipalName, "HTTP/[email protected]") - ... - require.Equal(t, info[1].PrincipalName, "HTTP/[email protected]") + m := map[string]utils.KeytabInfo{} + for _, it := range info { m[it.PrincipalName] = it } + it1, ok := m["HTTP/[email protected]"]; require.True(t, ok) + require.Len(t, it1.Pairs, 1) + require.Equal(t, uint8(2), it1.Pairs[0].Version) + require.Equal(t, int32(18), it1.Pairs[0].EncryptType) + require.Equal(t, tm.Unix(), it1.Pairs[0].CreateTime.Unix()) + it2, ok := m["HTTP/[email protected]"]; require.True(t, ok) + require.Len(t, it2.Pairs, 2) + // ...same assertions for it2spnego/v3/spnego_test.go (2)
24-41: Avoid brittle body equality; assert error classification and status only.Relying on the exact error message body may break across Fiber versions/config. Prefer status + substring or error class.
- require.Equal(t, http.StatusInternalServerError, ctx.Response.StatusCode()) - require.Equal(t, fmt.Sprintf("%s: mock keytab lookup error", spnego.ErrLookupKeytabFailed), string(ctx.Response.Body())) + require.Equal(t, http.StatusInternalServerError, ctx.Response.StatusCode()) + require.Contains(t, string(ctx.Response.Body()), spnego.ErrLookupKeytabFailed.Error()) + require.Contains(t, string(ctx.Response.Body()), "mock keytab lookup error")
74-88: Assert SPNEGO challenge headers on 401 to catch ResponseWriter adapter bugs.Add assertions for:
- WWW-Authenticate: Negotiate
- Vary: Authorization (recommended by RFC 7235 §4.1)
This will detect regressions where headers are written to the request header map instead of the response.handler(ctx) require.Equal(t, fasthttp.StatusUnauthorized, ctx.Response.StatusCode()) + require.Contains(t, string(ctx.Response.Header.Peek("WWW-Authenticate")), "Negotiate") + require.Contains(t, string(ctx.Response.Header.Peek("Vary")), "Authorization")spnego/utils/mock_keytab_test.go (2)
52-68: Rename and clarify: "no file output" case.Subtest name "none file created" is awkward; "no file persisted" is clearer. The checks look good.
- t.Run("test none file created", func(t *testing.T) { + t.Run("test no file persisted", func(t *testing.T) {
84-98: Same: restore global and avoid fixed path.Also, prefer a temp dir path to avoid collisions; the test currently asserts removal on failure, but creating "./temp.keytab" across runs can collide.
- defaultFileOperator = mockFileOperator{flag: 0x02} + prev := defaultFileOperator + defaultFileOperator = mockFileOperator{flag: 0x02} + t.Cleanup(func() { defaultFileOperator = prev }) ... - WithFilename("./temp.keytab"), + dir := t.TempDir() + WithFilename(filepath.Join(dir, "temp.keytab")),Also add import:
+ "path/filepath"spnego/utils/keytab.go (1)
73-76: Optional: also sort each principal’s Pairs for stable, deterministic outputIf the caller inspects
Pairs, consider sorting them (e.g., by EncryptType, then Version, then CreateTime). This makes outputs deterministic across runs and merges.sort.Slice(mk, func(i, j int) bool { return mk[i].PrincipalName < mk[j].PrincipalName }) +// Ensure deterministic order of pairs inside each principal +for i := range mk { + sort.Slice(mk[i].Pairs, func(a, b int) bool { + if mk[i].Pairs[a].EncryptType == mk[i].Pairs[b].EncryptType { + if mk[i].Pairs[a].Version == mk[i].Pairs[b].Version { + return mk[i].Pairs[a].CreateTime.Before(mk[i].Pairs[b].CreateTime) + } + return mk[i].Pairs[a].Version < mk[i].Pairs[b].Version + } + return mk[i].Pairs[a].EncryptType < mk[i].Pairs[b].EncryptType + }) +}spnego/config.go (2)
34-44: Optional: deduplicate entries across multiple keytabsIf the same entry exists in multiple files, the merged keytab will contain duplicates. Consider deduping by fields that uniquely identify a key entry (principal, enctype, version, timestamp).
return func() (*keytab.Keytab, error) { - var mergeKeytab keytab.Keytab + var mergeKeytab keytab.Keytab + seen := make(map[string]struct{}, 256) for _, keytabFile := range keytabFiles { kt, err := keytab.Load(keytabFile) if err != nil { return nil, fmt.Errorf("%w: file %s load failed: %w", ErrLoadKeytabFileFailed, keytabFile, err) } - mergeKeytab.Entries = append(mergeKeytab.Entries, kt.Entries...) + for _, e := range kt.Entries { + // principal | enctype | kvno | ts (unix seconds) – adjust if your uniqueness criteria differ + k := fmt.Sprintf("%s|%d|%d|%d", e.Principal.String(), e.Key.KeyType, e.KVNO8, e.Timestamp.Unix()) + if _, ok := seen[k]; ok { + continue + } + seen[k] = struct{}{} + mergeKeytab.Entries = append(mergeKeytab.Entries, e) + } } return &mergeKeytab, nil }, nil
28-45: Optional: add a caching wrapper to avoid re-reading files on every lookupIf
KeytabLookupis invoked per request or per handshake, consider an opt-in, TTL-based cache to reduce I/O, with a background refresh strategy.Example (new helper, can live alongside this file):
package spnego import ( "sync" "time" "github.com/jcmturner/gokrb5/v8/keytab" ) func NewCachedKeytabLookupFunc(loader KeytabLookupFunc, ttl time.Duration) KeytabLookupFunc { var ( mu sync.RWMutex kt *keytab.Keytab exp time.Time ) return func() (*keytab.Keytab, error) { now := time.Now() mu.RLock() if kt != nil && now.Before(exp) { defer mu.RUnlock() return kt, nil } mu.RUnlock() mu.Lock() defer mu.Unlock() if kt != nil && now.Before(exp) { // double-check return kt, nil } fresh, err := loader() if err != nil { return nil, err } kt = fresh exp = now.Add(ttl) return kt, nil } }spnego/utils/mock_keytab.go (2)
124-132: Wrap underlying error and validate inputs (principal/pairs) for clearer failures
- Use
%wto preserve error chains fromAddEntry.- Optionally fail fast when
PrincipalNameis empty or noPairsare provided; this prevents creating unusable keytabs in tests.opt := newDefaultMockOptions() opt.apply(opts...) kt := keytab.New() var err error +if opt.PrincipalName == "" { + return nil, nil, fmt.Errorf("principal name is required") +} +if len(opt.Pairs) == 0 { + return nil, nil, fmt.Errorf("at least one EncryptTypePair is required") +} for _, pair := range opt.Pairs { - if err = kt.AddEntry(opt.PrincipalName, opt.Realm, opt.Password, pair.CreateTime, pair.Version, pair.EncryptType); err != nil { - return nil, nil, fmt.Errorf("error adding entry: %v", err) + if err = kt.AddEntry(opt.PrincipalName, opt.Realm, opt.Password, pair.CreateTime, pair.Version, pair.EncryptType); err != nil { + return nil, nil, fmt.Errorf("add entry: %w", err) } }
92-103: Nit: clarify naming to reflect purpose
myFileOperator/defaultFileOperatorare fine, butosFileOperatorcommunicates intent slightly better.-type myFileOperator struct{} +type osFileOperator struct{} -func (m myFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) { +func (m osFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) { return os.OpenFile(filename, flag, perm) } -func (m myFileOperator) Remove(filename string) error { +func (m osFileOperator) Remove(filename string) error { return os.Remove(filename) } -var defaultFileOperator fileOperator = myFileOperator{} +var defaultFileOperator fileOperator = osFileOperator{}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (19)
spnego/README.md(1 hunks)spnego/README.zh-CN.md(1 hunks)spnego/config.go(1 hunks)spnego/config_test.go(1 hunks)spnego/doc.go(1 hunks)spnego/error.go(1 hunks)spnego/example/example.go(1 hunks)spnego/identity.go(1 hunks)spnego/identity_test.go(1 hunks)spnego/utils/adapter.go(1 hunks)spnego/utils/adapter_test.go(1 hunks)spnego/utils/keytab.go(1 hunks)spnego/utils/keytab_test.go(1 hunks)spnego/utils/mock_keytab.go(1 hunks)spnego/utils/mock_keytab_test.go(1 hunks)spnego/v2/spnego.go(1 hunks)spnego/v2/spnego_test.go(1 hunks)spnego/v3/spnego.go(1 hunks)spnego/v3/spnego_test.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- spnego/v2/spnego_test.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.
Applied to files:
spnego/utils/keytab_test.go
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
PR: gofiber/fiber#3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.
Applied to files:
spnego/utils/adapter_test.gospnego/utils/adapter.gospnego/v2/spnego.gospnego/v3/spnego.go
📚 Learning: 2024-11-15T07:56:21.623Z
Learnt from: ReneWerner87
PR: gofiber/fiber#3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.
Applied to files:
spnego/utils/adapter_test.go
📚 Learning: 2024-11-21T11:28:14.554Z
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-11-21T11:28:14.554Z
Learning: Using `fiber.Ctx`, even when wrapped, does not achieve version independence, as it still relies on the specific Fiber implementation.
Applied to files:
spnego/utils/adapter_test.go
📚 Learning: 2024-11-29T15:24:06.083Z
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-11-29T15:24:06.083Z
Learning: When customizing the Fiber context to achieve version independence, using generics in methods like `Status` and `Type` allows for fluent method chaining. Implementing a generic interface for `Ctx` on the `App` prevents class switching when registering custom contexts that use fluent methods. Providing multiple `New` methods allows users who do not wish to customize the context to continue using `fiber.New` without changes.
Applied to files:
spnego/utils/adapter_test.go
📚 Learning: 2024-09-25T15:57:10.221Z
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-09-25T15:57:10.221Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.
Applied to files:
spnego/v2/spnego.gospnego/v3/spnego.go
📚 Learning: 2025-05-13T00:19:16.407Z
Learnt from: sixcolors
PR: gofiber/fiber#3446
File: docs/middleware/logger.md:44-44
Timestamp: 2025-05-13T00:19:16.407Z
Learning: In documentation files for the Fiber framework, code examples are often partial and don't repeat import statements that were shown in earlier examples, focusing instead on demonstrating specific usage patterns.
Applied to files:
spnego/README.md
📚 Learning: 2024-11-23T19:50:41.765Z
Learnt from: norri
PR: gofiber/recipes#2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.
Applied to files:
spnego/README.md
🧬 Code graph analysis (13)
spnego/utils/keytab_test.go (2)
spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-150)WithRealm(40-44)WithPrincipal(32-36)WithPassword(67-71)WithPairs(58-62)spnego/utils/keytab.go (2)
EncryptTypePair(22-26)GetKeytabInfo(50-77)
spnego/identity_test.go (1)
spnego/identity.go (2)
SetAuthenticatedIdentityToContext(12-14)GetAuthenticatedIdentityFromContext(25-28)
spnego/utils/adapter_test.go (1)
spnego/utils/adapter.go (1)
NewWrapFiberContext(53-57)
spnego/example/example.go (5)
spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-150)WithPrincipal(32-36)WithRealm(40-44)WithFilename(49-53)WithPairs(58-62)spnego/utils/keytab.go (1)
EncryptTypePair(22-26)spnego/config.go (2)
NewKeytabFileLookupFunc(30-45)Config(21-26)spnego/v3/spnego.go (1)
NewSpnegoKrb5AuthenticateMiddleware(26-59)spnego/identity.go (1)
GetAuthenticatedIdentityFromContext(25-28)
spnego/config_test.go (4)
spnego/config.go (1)
NewKeytabFileLookupFunc(30-45)spnego/error.go (2)
ErrConfigInvalidOfAtLeastOneKeytabFileRequired(15-15)ErrLoadKeytabFileFailed(18-18)spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-150)WithPrincipal(32-36)WithRealm(40-44)WithPairs(58-62)WithFilename(49-53)spnego/utils/keytab.go (2)
EncryptTypePair(22-26)GetKeytabInfo(50-77)
spnego/identity.go (1)
spnego/utils/adapter.go (1)
FiberContext(12-19)
spnego/config.go (1)
spnego/error.go (2)
ErrConfigInvalidOfAtLeastOneKeytabFileRequired(15-15)ErrLoadKeytabFileFailed(18-18)
spnego/utils/mock_keytab_test.go (2)
spnego/utils/mock_keytab.go (6)
NewMockKeytab(123-150)WithPrincipal(32-36)WithRealm(40-44)WithPairs(58-62)WithFilename(49-53)WithPassword(67-71)spnego/utils/keytab.go (1)
EncryptTypePair(22-26)
spnego/utils/adapter.go (1)
spnego/identity.go (1)
FiberContext(5-7)
spnego/utils/mock_keytab.go (1)
spnego/utils/keytab.go (1)
EncryptTypePair(22-26)
spnego/v2/spnego.go (6)
spnego/v3/spnego.go (1)
NewSpnegoKrb5AuthenticateMiddleware(26-59)spnego/config.go (1)
Config(21-26)spnego/config/config.go (4)
Config(37-42)ErrConfigInvalidOfKeytabLookupFunctionRequired(12-12)ErrLookupKeytabFailed(15-15)ErrConvertRequestFailed(18-18)spnego/error.go (3)
ErrConfigInvalidOfKeytabLookupFunctionRequired(6-6)ErrLookupKeytabFailed(9-9)ErrConvertRequestFailed(12-12)spnego/identity.go (1)
SetAuthenticatedIdentityToContext(12-14)spnego/utils/adapter.go (1)
NewWrapFiberContext(53-57)
spnego/v3/spnego.go (6)
spnego/v2/spnego.go (1)
NewSpnegoKrb5AuthenticateMiddleware(26-60)spnego/config.go (1)
Config(21-26)spnego/config/config.go (4)
Config(37-42)ErrConfigInvalidOfKeytabLookupFunctionRequired(12-12)ErrLookupKeytabFailed(15-15)ErrConvertRequestFailed(18-18)spnego/error.go (3)
ErrConfigInvalidOfKeytabLookupFunctionRequired(6-6)ErrLookupKeytabFailed(9-9)ErrConvertRequestFailed(12-12)spnego/identity.go (1)
SetAuthenticatedIdentityToContext(12-14)spnego/utils/adapter.go (1)
NewWrapFiberContext(53-57)
spnego/v3/spnego_test.go (4)
spnego/v3/spnego.go (1)
NewSpnegoKrb5AuthenticateMiddleware(26-59)spnego/config.go (2)
Config(21-26)NewKeytabFileLookupFunc(30-45)spnego/utils/mock_keytab.go (1)
NewMockKeytab(123-150)spnego/identity.go (1)
GetAuthenticatedIdentityFromContext(25-28)
🪛 LanguageTool
spnego/README.md
[grammar] ~17-~17: There might be a mistake here.
Context: ...eros authentication via SPNEGO mechanism - Flexible keytab lookup system - Support ...
(QB_NEW_EN)
[grammar] ~18-~18: There might be a mistake here.
Context: ...echanism - Flexible keytab lookup system - Support for dynamic keytab retrieval fro...
(QB_NEW_EN)
[grammar] ~19-~19: There might be a mistake here.
Context: ...ic keytab retrieval from various sources - Integration with Fiber context for authe...
(QB_NEW_EN)
[grammar] ~20-~20: There might be a mistake here.
Context: ...ntext for authenticated identity storage - Configurable logging ## Version Compati...
(QB_NEW_EN)
[grammar] ~27-~27: There might be a mistake here.
Context: ...ses: - v2: Compatible with Fiber v2 - v3: Compatible with Fiber v3 ## Insta...
(QB_NEW_EN)
[grammar] ~184-~184: There might be a mistake here.
Context: ...ion that retrieves the keytab (required) - Log: The logger used for middleware logging...
(QB_NEW_EN)
[grammar] ~189-~189: There might be a mistake here.
Context: ...r) ## Requirements - Go 1.21 or higher - For v3: Fiber v3 - For v2: Fiber v2 - Ke...
(QB_NEW_EN)
[grammar] ~190-~190: There might be a mistake here.
Context: ... - Go 1.21 or higher - For v3: Fiber v3 - For v2: Fiber v2 - Kerberos infrastructu...
(QB_NEW_EN)
[grammar] ~191-~191: There might be a mistake here.
Context: ...er - For v3: Fiber v3 - For v2: Fiber v2 - Kerberos infrastructure ## Notes - Ens...
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.17.2)
spnego/README.md
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
spnego/README.zh-CN.md
190-190: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (8)
spnego/README.md (1)
32-38: Align SPNEGO module import paths across code and docsI’ve spotted mixed import patterns in the SPNEGO example and README files:
- Unversioned imports:
github.com/gofiber/contrib/spnegogithub.com/gofiber/contrib/spnego/utils- Versioned imports:
github.com/gofiber/contrib/spnego/v2github.com/gofiber/contrib/spnego/v3Please pick one repository‐wide pattern and make it consistent everywhere:
• Confirm the module path declared in the SPNEGO Go module (e.g. in
spnego/go.mod)—it should include the major version suffix if you’re using Go modules (for v3, for example, it should readmodule github.com/gofiber/contrib/spnego/v3).
• Update all installation commands inspnego/README.mdandspnego/README.zh-CN.mdto usego get github.com/gofiber/contrib/spnego/v{N}.
• Update all import statements (inspnego/example/example.go, README snippets, etc.) to match that same pattern—including nested packages, e.g.github.com/gofiber/contrib/spnego/v3/utilsrather than the unversioned path.
• Rungo mod tidy(or your workspace setup) to validate that the chosen import paths resolve correctly.Once imports and docs are fully aligned to the module’s declared path, this will avoid confusion and ensure smooth version upgrades.
spnego/v3/spnego_test.go (1)
19-22: Nil KeytabLookup validation looks good.This asserts the required config gate appropriately.
spnego/utils/mock_keytab_test.go (2)
35-51: Test name clarity and deterministic timing."test add keytab entry failed" is fine; consider fixing create times to tm := time.Unix(...) for deterministic snapshots if needed. No action required.
130-155: Functional options tests look good; minor style nit.Option application order and content are validated well. No issues.
spnego/v2/spnego.go (1)
31-35: Sane default logger in v2 looks good.Creating a fresh *log.Logger avoids unsafe assertions. No changes needed.
spnego/utils/keytab.go (2)
51-68: Aggregation logic is clear and correctGrouping entries by
Principal.String()and accumulatingEncryptTypePairlooks good. Using a temporary map and then materializing a slice is appropriate before sorting.
6-7: Confirm Go 1.21+ requirement formapsimportThe new import of the standard
mapspackage (spnego/utils/keytab.go, lines 6-7) requires Go 1.21 or newer. I wasn’t able to find ago.modorgo.workdeclaring a minimum Go version, nor any CI workflows or Dockerfiles pinning the Go image. Please verify that:
- A
go.mod(orgo.work) file exists at the repository root (or relevant module directory) withor higher.go 1.21- Your CI pipelines (e.g. GitHub Actions, Travis, etc.) and any Dockerfiles use a Go 1.21+ image.
- If you’re not using modules, that your build environment is still configured to use Go 1.21 or later.
Ensuring these will prevent build errors when importing
maps.spnego/config.go (1)
30-45: Behavior is sound; loader composes multiple keytabs correctlyValidates input, loads files, merges entries, and wraps errors with useful context. API shape (variadic files, closure returning merged keytab) is clean.
| t.Run("test one keytab file", func(t *testing.T) { | ||
| tm := time.Now() | ||
| _, clean, err := utils.NewMockKeytab( | ||
| utils.WithPrincipal("HTTP/sso.example.com"), | ||
| utils.WithRealm("TEST.LOCAL"), | ||
| utils.WithPairs(utils.EncryptTypePair{ | ||
| Version: 2, | ||
| EncryptType: 18, | ||
| CreateTime: tm, | ||
| }), | ||
| utils.WithFilename("./temp.keytab"), | ||
| ) | ||
| require.NoError(t, err) | ||
| t.Cleanup(clean) | ||
| fn, err := NewKeytabFileLookupFunc("./temp.keytab") | ||
| require.NoError(t, err) | ||
| kt1, err := fn() | ||
| require.NoError(t, err) | ||
| info := utils.GetKeytabInfo(kt1) | ||
| require.Len(t, info, 1) | ||
| require.Equal(t, info[0].PrincipalName, "HTTP/[email protected]") | ||
| require.Equal(t, info[0].Realm, "TEST.LOCAL") | ||
| require.Len(t, info[0].Pairs, 1) | ||
| require.Equal(t, info[0].Pairs[0].Version, uint8(2)) | ||
| require.Equal(t, info[0].Pairs[0].EncryptType, int32(18)) | ||
| // Note: The creation time of keytab is only accurate to the second. | ||
| require.Equal(t, info[0].Pairs[0].CreateTime.Unix(), tm.Unix()) | ||
| }) |
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.
🛠️ Refactor suggestion
Harden test keytab creation and remove flakiness.
- File handling: utils.NewMockKeytab persists with 0o666 in current impl, which is too permissive for keytab material. Use a temp directory and file (t.TempDir) and update the helper to use 0o600.
- Time checks: You already compare at second granularity; good. Keep using the same tm for all entries in this case.
- utils.WithFilename("./temp.keytab"),
+ dir := t.TempDir()
+ utils.WithFilename(filepath.Join(dir, "temp.keytab")),Follow-up change in spnego/utils/mock_keytab.go (outside this file):
- file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE, 0o666)
+ file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)Committable suggestion skipped: line range outside the PR's diff.
v3/spnego/example/example.go
Outdated
| func ExampleNewSpnegoKrb5AuthenticateMiddleware() { | ||
| app := fiber.New() | ||
| // create mock keytab file | ||
| // you must use a real keytab file | ||
| _, clean, err := utils.NewMockKeytab( | ||
| utils.WithPrincipal("HTTP/sso1.example.com"), | ||
| utils.WithRealm("EXAMPLE.LOCAL"), | ||
| utils.WithFilename("./temp-sso1.keytab"), | ||
| utils.WithPairs(utils.EncryptTypePair{ | ||
| Version: 2, | ||
| EncryptType: 18, | ||
| CreateTime: time.Now(), | ||
| }), | ||
| ) | ||
| if err != nil { | ||
| log.Fatalf("create mock keytab error: %v", err) | ||
| } | ||
| defer clean() | ||
| keytabLookup, err := spnego.NewKeytabFileLookupFunc("./temp-sso1.keytab") | ||
| if err != nil { | ||
| panic(fmt.Errorf("create keytab lookup function failed: %w", err)) | ||
| } | ||
| authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{ | ||
| KeytabLookup: keytabLookup, | ||
| }) | ||
| if err != nil { | ||
| panic(fmt.Errorf("create spnego middleware failed: %w", err)) | ||
| } | ||
| // Apply the middleware to protected routes | ||
| app.Use("/protected", authMiddleware) | ||
|
|
||
| // Access authenticated identity | ||
| app.Get("/protected/resource", func(c fiber.Ctx) error { | ||
| identity, ok := spnego.GetAuthenticatedIdentityFromContext(c) | ||
| if !ok { | ||
| return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized") | ||
| } | ||
| return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName())) | ||
| }) | ||
| log.Info("Server is running on :3000") | ||
| go func() { | ||
| <-time.After(time.Second * 1) | ||
| fmt.Println("use curl -kv --negotiate http://sso.example.local:3000/protected/resource") | ||
| fmt.Println("if response is 401, execute `klist` to check use kerberos session") | ||
| <-time.After(time.Second * 2) | ||
| fmt.Println("close server") | ||
| if err = app.Shutdown(); err != nil { | ||
| panic(fmt.Errorf("shutdown server failed: %w", err)) | ||
| } | ||
| }() | ||
| if err := app.Listen("sso.example.local:3000"); err != nil { | ||
| panic(fmt.Errorf("start server failed: %w", err)) | ||
| } | ||
|
|
||
| // Output: Server is running on :3000 | ||
| } |
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.
🛠️ Refactor suggestion
Example will fail as a doc test (“Output: …”) and/or hang CI.
- The function is an Example with an
Output:comment, sogo testwill run it and compare stdout. However:- It starts a server and prints additional lines via
fmt.Println, which won’t match the single-line expected output. log.Info(Fiber logger) may not write to stdout in a waygo testcaptures.- Binding to
sso.example.local:3000can fail name resolution in CI.
Fix by removing theOutput:directive (examples without Output are compiled but not run), or simplify to a non-network example that only prints the expected line.
- It starts a server and prints additional lines via
Minimal safe fix (don’t execute the example in tests):
- // Output: Server is running on :3000
+ // (no Output: directive on purpose; this example should compile but not run during `go test`)Optional follow-ups:
- Change the listen address to
:3000orlocalhost:3000. - If you prefer keeping an executable example, remove the server code and just
fmt.Println("Server is running on :3000")to satisfy theOutput:check.
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
if err := app.Listen("sso.example.local:3000"); err != nil {
panic(fmt.Errorf("start server failed: %w", err))
}
// (no Output: directive on purpose; this example should compile but not run during `go test`)
}
🤖 Prompt for AI Agents
In spnego/example/example.go around lines 14-69 the Example function starts a
real server, does network calls and prints multiple lines but also includes an
`// Output: Server is running on :3000` doctest directive which makes `go test`
try to run and match stdout (causing failures/hangs in CI); fix by removing the
`Output:` directive so the example is compiled but not executed by tests, or
alternatively convert the example to a non-network doc example by removing
server startup and ancillary prints and leaving a single `fmt.Println("Server is
running on :3000")` that matches the `Output:` line (if you keep an Output,
ensure only that exact line is printed), and optionally change the listen
address to `:3000` or `localhost:3000` if you intend to run network code
locally.
| func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) { | ||
| app := fiberV3.New() | ||
| id := goidentity.NewUser("[email protected]") | ||
| app.Use("/identity", func(ctx fiberV3.Ctx) error { | ||
| SetAuthenticatedIdentityToContext(ctx, &id) | ||
| return ctx.Next() | ||
| }) | ||
| app.Get("/test", func(ctx fiberV3.Ctx) error { | ||
| _, ok := GetAuthenticatedIdentityFromContext(ctx) | ||
| require.False(t, ok) | ||
| return ctx.SendStatus(fiberV3.StatusOK) | ||
| }) | ||
| app.Get("/identity/test", func(ctx fiberV3.Ctx) error { | ||
| user, ok := GetAuthenticatedIdentityFromContext(ctx) | ||
| require.True(t, ok) | ||
| require.Equal(t, id.UserName(), user.UserName()) | ||
| require.Equal(t, id.Domain(), user.Domain()) | ||
| return ctx.SendStatus(fiberV3.StatusOK) | ||
| }) | ||
| app.Test(httptest.NewRequest("GET", "/test", nil)) | ||
| app.Test(httptest.NewRequest("GET", "/identity/test", 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.
🛠️ Refactor suggestion
Mirror the same robustness for Fiber v3 test.
Parallelize and verify responses.
func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) {
+ t.Parallel()
@@
- app.Test(httptest.NewRequest("GET", "/test", nil))
- app.Test(httptest.NewRequest("GET", "/identity/test", nil))
+ resp1, err := app.Test(httptest.NewRequest("GET", "/test", nil))
+ require.NoError(t, err)
+ defer resp1.Body.Close()
+ require.Equal(t, fiberV3.StatusOK, resp1.StatusCode)
+
+ resp2, err := app.Test(httptest.NewRequest("GET", "/identity/test", nil))
+ require.NoError(t, err)
+ defer resp2.Body.Close()
+ require.Equal(t, fiberV3.StatusOK, resp2.StatusCode)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) { | |
| app := fiberV3.New() | |
| id := goidentity.NewUser("[email protected]") | |
| app.Use("/identity", func(ctx fiberV3.Ctx) error { | |
| SetAuthenticatedIdentityToContext(ctx, &id) | |
| return ctx.Next() | |
| }) | |
| app.Get("/test", func(ctx fiberV3.Ctx) error { | |
| _, ok := GetAuthenticatedIdentityFromContext(ctx) | |
| require.False(t, ok) | |
| return ctx.SendStatus(fiberV3.StatusOK) | |
| }) | |
| app.Get("/identity/test", func(ctx fiberV3.Ctx) error { | |
| user, ok := GetAuthenticatedIdentityFromContext(ctx) | |
| require.True(t, ok) | |
| require.Equal(t, id.UserName(), user.UserName()) | |
| require.Equal(t, id.Domain(), user.Domain()) | |
| return ctx.SendStatus(fiberV3.StatusOK) | |
| }) | |
| app.Test(httptest.NewRequest("GET", "/test", nil)) | |
| app.Test(httptest.NewRequest("GET", "/identity/test", nil)) | |
| } | |
| func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) { | |
| t.Parallel() | |
| app := fiberV3.New() | |
| id := goidentity.NewUser("[email protected]") | |
| app.Use("/identity", func(ctx fiberV3.Ctx) error { | |
| SetAuthenticatedIdentityToContext(ctx, &id) | |
| return ctx.Next() | |
| }) | |
| app.Get("/test", func(ctx fiberV3.Ctx) error { | |
| _, ok := GetAuthenticatedIdentityFromContext(ctx) | |
| require.False(t, ok) | |
| return ctx.SendStatus(fiberV3.StatusOK) | |
| }) | |
| app.Get("/identity/test", func(ctx fiberV3.Ctx) error { | |
| user, ok := GetAuthenticatedIdentityFromContext(ctx) | |
| require.True(t, ok) | |
| require.Equal(t, id.UserName(), user.UserName()) | |
| require.Equal(t, id.Domain(), user.Domain()) | |
| return ctx.SendStatus(fiberV3.StatusOK) | |
| }) | |
| resp1, err := app.Test(httptest.NewRequest("GET", "/test", nil)) | |
| require.NoError(t, err) | |
| defer resp1.Body.Close() | |
| require.Equal(t, fiberV3.StatusOK, resp1.StatusCode) | |
| resp2, err := app.Test(httptest.NewRequest("GET", "/identity/test", nil)) | |
| require.NoError(t, err) | |
| defer resp2.Body.Close() | |
| require.Equal(t, fiberV3.StatusOK, resp2.StatusCode) | |
| } |
🤖 Prompt for AI Agents
In spnego/identity_test.go around lines 36 to 57, the Fiber v3 test should
mirror the more robust Fiber v2 test by running subtests in parallel and
asserting responses; add t.Parallel() at the start of the test, invoke app.Test
for "/test" and "/identity/test" in separate goroutines or use subtests with
t.Run(..., func(t *testing.T){ t.Parallel() ... }), capture and require.NoError
on the returned responses and errors, assert resp.StatusCode == fiberV3.StatusOK
for both requests, and ensure resp.Body is closed after reads; this will
parallelize execution and verify the responses rather than calling app.Test
without checks.
| func SetAuthenticatedIdentityToContext[T FiberContext](ctx T, identity goidentity.Identity) { | ||
| ctx.Locals(contextKeyOfIdentity, identity) | ||
| } |
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.
💡 Verification agent
🧩 Analysis chain
Verify context key definition; otherwise these helpers won’t compile.
Both helpers rely on an unexported contextKeyOfIdentity. Confirm it exists in package spnego and isn’t typed as a plain string to avoid collisions.
Script to confirm presence once in this package:
If missing, add:
// prefer an unexported, unique key type to avoid collisions with user code
type contextKey string
var contextKeyOfIdentity contextKey = "spnego.identity"🏁 Script executed:
#!/bin/bash
rg -nP '^\s*(type\s+contextKey\b|var\s+contextKeyOfIdentity\b|const\s+contextKeyOfIdentity\b)' spnego -C2Length of output: 436
Ensure a unique, typed context key to avoid collisions
The contextKeyOfIdentity constant is defined in spnego/config.go:11, but it’s an untyped string, which can collide with other context keys. Update it to use a dedicated key type:
• File: spnego/config.go (around line 11)
- Current:
// contextKeyOfIdentity is the key used to store the authenticated identity in the Fiber context const contextKeyOfIdentity = "middleware.spnego.Identity"
- Replace with a private
contextKeytype and a typed variable:
// prefer an unexported, unique key type to avoid collisions with user code
+type contextKey string
-// contextKeyOfIdentity is the key used to store the authenticated identity in the Fiber context
-const contextKeyOfIdentity = "middleware.spnego.Identity"
+var contextKeyOfIdentity contextKey = "middleware.spnego.Identity"This ensures that only values using your contextKey type can collide, preventing accidental overlaps with other packages’ string-based keys.
🤖 Prompt for AI Agents
In spnego/config.go around line 11, replace the untyped string constant
contextKeyOfIdentity with a private typed context key to avoid collisions:
declare a new private type (e.g. type contextKey string) and then define a
package-level variable of that type for the identity key (e.g. var
contextKeyOfIdentity = contextKey("middleware.spnego.Identity")), then update
uses (like in spnego/identity.go lines 12-14) to continue using the variable;
ensure the new type and variable are unexported so only your package can use
that key.
| t.Run("test file created", func(t *testing.T) { | ||
| defaultFileOperator = myFileOperator{} | ||
| tm := time.Now() | ||
| _, clean, err := NewMockKeytab( | ||
| WithPrincipal("HTTP/sso.example.com"), | ||
| WithRealm("TEST.LOCAL"), | ||
| WithPairs(EncryptTypePair{ | ||
| Version: 3, | ||
| EncryptType: 18, | ||
| CreateTime: tm, | ||
| }), | ||
| WithFilename("./temp.keytab"), | ||
| ) | ||
| require.NoError(t, err) | ||
| t.Cleanup(clean) | ||
| require.FileExists(t, "./temp.keytab") | ||
| kt, err := keytab.Load("./temp.keytab") | ||
| require.NoError(t, err) | ||
| _, kv, err := kt.GetEncryptionKey(types.NewPrincipalName(1, "HTTP/sso.example.com"), "TEST.LOCAL", 3, 18) | ||
| require.NoError(t, err) | ||
| require.Equal(t, 3, kv) | ||
| }) |
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.
🛠️ Refactor suggestion
Use secure permission and temp path in the positive case too.
Ensure the production path uses mode 0o600 (change in helper required) and avoid ./temp.keytab.
- defaultFileOperator = myFileOperator{}
+ prev := defaultFileOperator
+ defaultFileOperator = myFileOperator{}
+ t.Cleanup(func() { defaultFileOperator = prev })
...
- WithFilename("./temp.keytab"),
+ dir := t.TempDir()
+ WithFilename(filepath.Join(dir, "temp.keytab")),And in spnego/utils/mock_keytab.go (outside this file):
- defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE, 0o666)
+ defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)Committable suggestion skipped: line range outside the PR's diff.
test: Update test cases to use temporary directory instead of current directory refactor: Remove unused config package
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.
Actionable comments posted: 0
♻️ Duplicate comments (1)
spnego/utils/mock_keytab.go (1)
135-147: Close-before-remove on write error; tighten perms; open WRONLY (Windows-safe and credential-safe)
On write error the file is removed while still open; this can fail on Windows and leak a descriptor. Also, using 0o666 for keytab files is overly permissive for credential material; prefer 0o600. Opening WRONLY clarifies intent.Apply this diff:
- file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666) + file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { return nil, nil, fmt.Errorf("error opening file: %w", err) } - clean = func() { - _ = defaultFileOperator.Remove(opt.Filename) - } + // cleanup closes and removes the file; safe to call multiple times + clean = func() { + _ = file.Close() + _ = defaultFileOperator.Remove(opt.Filename) + } if _, err = kt.Write(file); err != nil { - clean() + clean() // ensures close-before-remove on error (important on Windows) return nil, nil, fmt.Errorf("error writing to file: %w", err) } _ = file.Close() return kt, clean, nilOptional verification to catch other overly-permissive creations:
#!/bin/bash # Find any file creations with 0o666 perms or OpenFile calls using O_RDWR where write-only suffices. rg -nP '0o666\b' -C2 rg -nP 'OpenFile\([^,]+,\s*os\.O_RDWR\b' -C2
🧹 Nitpick comments (3)
spnego/utils/mock_keytab.go (3)
126-133: Validate required fields when adding entriesIf Pairs is non-empty but PrincipalName is empty, gokrb5 may accept an invalid principal or error in surprising ways. Fail fast with a clear error.
Apply this diff:
kt := keytab.New() var err error + if len(opt.Pairs) > 0 && opt.PrincipalName == "" { + return nil, nil, fmt.Errorf("principal name must be set when adding keytab entries") + } for _, pair := range opt.Pairs {Optionally, add a unit test asserting that NewMockKeytab(WithPairs(...)) without WithPrincipal returns an error.
129-131: Wrap underlying error with %w for consistencyUse %w so callers can use errors.Is / errors.As.
Apply this diff:
- return nil, nil, fmt.Errorf("error adding entry: %v", err) + return nil, nil, fmt.Errorf("error adding entry: %w", err)
92-103: Minor naming nit: clarify file operator implementation
Rename myFileOperator to osFileOperator to reflect it delegates to the os package.Apply this diff:
-type myFileOperator struct{} +type osFileOperator struct{} -func (m myFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) { +func (m osFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) { return os.OpenFile(filename, flag, perm) } -func (m myFileOperator) Remove(filename string) error { +func (m osFileOperator) Remove(filename string) error { return os.Remove(filename) } -var defaultFileOperator fileOperator = myFileOperator{} +var defaultFileOperator fileOperator = osFileOperator{}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
spnego/utils/mock_keytab.go(1 hunks)spnego/utils/mock_keytab_test.go(1 hunks)spnego/v2/spnego_test.go(1 hunks)spnego/v3/spnego_test.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- spnego/utils/mock_keytab_test.go
- spnego/v3/spnego_test.go
- spnego/v2/spnego_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
spnego/utils/mock_keytab.go (1)
spnego/utils/keytab.go (1)
EncryptTypePair(22-26)
🔇 Additional comments (1)
spnego/utils/mock_keytab.go (1)
10-19: Good use of the Options pattern and solid documentationClear option constructors, defaults, and comments. This makes the helper flexible and easy to use in tests.
Also applies to: 21-29, 30-76
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.
Actionable comments posted: 0
♻️ Duplicate comments (1)
spnego/utils/mock_keytab.go (1)
135-148: Harden file creation: WRONLY, 0600 perms, idempotent cleanup, and fsync
- Open with O_WRONLY (not O_RDWR) since we don't read; keep O_TRUNC.
- Use 0o600 for credential-like files (even in tests) to avoid permissive modes under loose umasks.
- Make cleanup close+remove to be idempotent and safe if called on error or success.
- fsync before close to reduce flakiness on slow CI filesystems.
- file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666) + // Write-only, create, and truncate with restrictive perms. + file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { return nil, nil, fmt.Errorf("error opening file: %w", err) } - clean = func() { - _ = defaultFileOperator.Remove(opt.Filename) - } + // Idempotent cleanup: close then remove; safe to call multiple times. + clean = func() { + _ = file.Close() + _ = defaultFileOperator.Remove(opt.Filename) + } if _, err = kt.Write(file); err != nil { - file.Close() - clean() + clean() // ensures close-before-remove on error return nil, nil, fmt.Errorf("error writing to file: %w", err) } - _ = file.Close() + // Best effort flush then close; leave remove to returned cleanup. + if err := file.Sync(); err != nil { + clean() + return nil, nil, fmt.Errorf("error syncing file: %w", err) + } + _ = file.Close() return kt, clean, nilNote: You already addressed O_TRUNC and close-before-remove per the earlier review; this refines flags, perms, and idempotency.
🧹 Nitpick comments (3)
spnego/utils/mock_keytab.go (3)
124-132: Add basic option validation to prevent unusable keytabsGuard against missing principal/realm and optionally empty pairs to avoid surprising runtime errors.
Apply this diff to introduce validation right after applying options:
func NewMockKeytab(opts ...MockOption) (*keytab.Keytab, func(), error) { opt := newDefaultMockOptions() opt.apply(opts...) + if opt.PrincipalName == "" { + return nil, nil, fmt.Errorf("principal name is required; set WithPrincipal(...)") + } + if opt.Realm == "" { + return nil, nil, fmt.Errorf("realm is required; set WithRealm(...)") + } + // If an empty keytab is not desirable for your tests, uncomment the guard below: + // if len(opt.Pairs) == 0 { + // return nil, nil, fmt.Errorf("at least one EncryptTypePair is required; set WithPairs(...)") + // } kt := keytab.New() var err error for _, pair := range opt.Pairs {If you intentionally rely on empty pairs in some tests, keep the third check commented or toggle it via an option.
128-131: Use %w for proper error wrappingPreserve the original error in the chain for callers using errors.Is/As.
- if err = kt.AddEntry(opt.PrincipalName, opt.Realm, opt.Password, pair.CreateTime, pair.Version, pair.EncryptType); err != nil { - return nil, nil, fmt.Errorf("error adding entry: %v", err) + if err = kt.AddEntry(opt.PrincipalName, opt.Realm, opt.Password, pair.CreateTime, pair.Version, pair.EncryptType); err != nil { + return nil, nil, fmt.Errorf("error adding entry: %w", err) }
92-103: Name nit: myFileOperator → osFileOperator; consider exposing a setter for testsMinor clarity nit on the concrete type name. Also, external tests (utils_test package) can’t mutate unexported vars; a tiny setter keeps the seam while maintaining encapsulation.
-type myFileOperator struct{} +type osFileOperator struct{} -func (m myFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) { +func (m osFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) { return os.OpenFile(filename, flag, perm) } -func (m myFileOperator) Remove(filename string) error { +func (m osFileOperator) Remove(filename string) error { return os.Remove(filename) } -var defaultFileOperator fileOperator = myFileOperator{} +var defaultFileOperator fileOperator = osFileOperator{}Optionally add (outside this hunk):
// SetFileOperator allows tests in external packages to inject a fake FS. func SetFileOperator(op fileOperator) { if op != nil { defaultFileOperator = op } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
spnego/utils/mock_keytab.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
spnego/utils/mock_keytab.go (1)
spnego/utils/keytab.go (1)
EncryptTypePair(22-26)
🔇 Additional comments (2)
spnego/utils/mock_keytab.go (2)
30-76: LGTM: clean, idiomatic option pattern with helpful docsThe option functions are straightforward and composable; docs make intended usage obvious.
104-123: File-backed path testing confirmedThe
WithFilenameoption is exercised in multiple test suites covering both successful and failure scenarios:
Happy paths in
spnego/v3/spnego_test.go(lines 50–52, 62–64)spnego/utils/mock_keytab_test.go(lines 119–121)spnego/config_test.go(lines 38–40, 66–68, 90–92, 106–108)spnego/v2/spnego_test.go(lines 50–52, 62–64)Failure paths in
spnego/utils/mock_keytab_test.go(lines 84–86 expectingos.ErrPermission; lines 103–105 expectingos.ErrClosed)No additional tests are needed for the file-backed keytab path.
- Migrate spnego middleware to v3 directory - Update package name to github.com/gofiber/contrib/v3/spnego - Remove support for Fiber v2 entirely - Update Fiber dependency to v3.0.0-rc.2 - Update README.md and README.zh-CN.md documentation - Update Go version requirement to 1.25 or higher
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.
Actionable comments posted: 4
♻️ Duplicate comments (7)
v3/spnego/identity_test.go (1)
12-33: Verify test responses and consider parallelization.Lines 31-32 call
app.Test()but ignore the returned response and error. Best practice requires verifying that the requests succeeded.Apply:
func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) { + t.Parallel() app := fiberV3.New() ... - app.Test(httptest.NewRequest("GET", "/test", nil)) - app.Test(httptest.NewRequest("GET", "/identity/test", nil)) + resp1, err := app.Test(httptest.NewRequest("GET", "/test", nil)) + require.NoError(t, err) + require.Equal(t, fiberV3.StatusOK, resp1.StatusCode) + resp1.Body.Close() + + resp2, err := app.Test(httptest.NewRequest("GET", "/identity/test", nil)) + require.NoError(t, err) + require.Equal(t, fiberV3.StatusOK, resp2.StatusCode) + resp2.Body.Close()v3/spnego/utils/adapter.go (3)
9-19: Critical: FiberContext interface doesn't unify v2/v3 due to Write signature mismatch.Fiber v2's
Ctx.Writereturnserror, not(int, error), so this interface constraint won't match both versions. The Write method should be removed from the interface and implemented usingResponse().AppendBody(bytes)instead.
28-37: Critical: Header() returns a detached copy; mutations are lost.The code calls
f.ctx.Response().Header.All()which doesn't exist in fasthttp. Even if it did, returning a freshhttp.Headermeans subsequent calls likew.Header().Set("WWW-Authenticate", ...)won't affect the Fiber response — breaking SPNEGO authentication.The adapter must maintain a single
http.Headerinstance and flush mutations to the underlying fasthttp response duringWriteHeader()or firstWrite(). UseHeader.VisitAll()to initialize and forward Set/Add/Del operations tof.ctx.Response().Header.
39-47: Critical: Write/WriteHeader don't follow net/http semantics.Per net/http,
WriteHeadermust be called (explicitly or implicitly with status 200) before the first body write, and headers must be flushed at that point. The current implementation doesn't trackwroteHeaderstate or apply accumulated headers.Implement:
- Store
wroteHeader boolandstatusCode intin the struct- In
Write(): if!wroteHeader, callWriteHeader(http.StatusOK)first, then useResponse().AppendBody(p)and returnlen(p), nil- In
WriteHeader(): flush accumulated headers fromHeader()toResponse().Header, callStatus(statusCode), and setwroteHeader = truev3/spnego/config.go (1)
10-11: Use a typed context key to prevent collisions.The
contextKeyOfIdentityis currently an untyped string constant, which can collide with other context keys. Use a typed, unexported key type for safety.Apply this diff:
+type contextKey string + -// contextKeyOfIdentity is the key used to store the authenticated identity in the Fiber context -const contextKeyOfIdentity = "middleware.spnego.Identity" +// contextKeyOfIdentity is the key used to store the authenticated identity in the Fiber context +var contextKeyOfIdentity contextKey = "middleware.spnego.Identity"v3/spnego/example/example.go (1)
13-68: Remove Output directive to prevent test execution.The
// Output:directive causesgo testto execute this example and match stdout. However, this example starts a real server and prints multiple lines, which will fail or hang in CI. Remove the Output directive so the example is compiled but not executed.Apply this diff:
- // Output: Server is running on :3000 + // This example demonstrates SPNEGO middleware usage. + // To run manually: go run example.gov3/spnego/utils/keytab.go (1)
69-77: Fix range iteration over map values.Line 70 uses a single-variable range over
maps.Values(keytabMap), which iterates over indices (int) rather than values (KeytabInfo). This will cause incorrect behavior.Apply this diff:
var mk = make(MultiKeytabInfo, 0, len(keytabMap)) - for item := range maps.Values(keytabMap) { + for _, item := range maps.Values(keytabMap) { mk = append(mk, item) }
🧹 Nitpick comments (6)
v3/spnego/README.zh-CN.md (1)
42-90: Replace hard tabs with spaces in code examples.The markdown linter flags hard tabs throughout the code examples (lines 42-90). Markdown best practices and most repository style guides require spaces for indentation.
Apply consistent spacing (typically 2 or 4 spaces) to replace tabs in the Go code examples.
v3/spnego/config_test.go (3)
17-27: Use octal literal prefix and temp directory.For consistency with modern Go and to avoid polluting the workspace, use
0o600for file permissions andt.TempDir()for test file location.Apply this diff:
t.Run("test not found keytab file", func(t *testing.T) { - err := os.WriteFile("./invalid.keytab", []byte("12345"), 0600) + invalidPath := filepath.Join(t.TempDir(), "invalid.keytab") + err := os.WriteFile(invalidPath, []byte("12345"), 0o600) require.NoError(t, err) - t.Cleanup(func() { - os.Remove("./invalid.keytab") - }) - fn, err := NewKeytabFileLookupFunc("./invalid.keytab") + fn, err := NewKeytabFileLookupFunc(invalidPath) require.NoError(t, err) _, err = fn() require.ErrorIs(t, err, ErrLoadKeytabFileFailed) })
28-55: Use temp directory for test keytab files.Using
t.TempDir()instead of the current directory prevents test artifacts from polluting the workspace and ensures proper cleanup.Apply this diff:
t.Run("test one keytab file", func(t *testing.T) { tm := time.Now() + tempFile := filepath.Join(t.TempDir(), "temp.keytab") _, clean, err := utils.NewMockKeytab( utils.WithPrincipal("HTTP/sso.example.com"), utils.WithRealm("TEST.LOCAL"), utils.WithPairs(utils.EncryptTypePair{ Version: 2, EncryptType: 18, CreateTime: tm, }), - utils.WithFilename("./temp.keytab"), + utils.WithFilename(tempFile), ) require.NoError(t, err) t.Cleanup(clean) - fn, err := NewKeytabFileLookupFunc("./temp.keytab") + fn, err := NewKeytabFileLookupFunc(tempFile)
56-79: Use temp directory and octal literal prefix.Apply the same improvements: use
t.TempDir()for test files and0o600for file permissions.Apply this diff:
t.Run("test multiple keytab file but has invalid keytab", func(t *testing.T) { tm := time.Now() + tempFile := filepath.Join(t.TempDir(), "temp.keytab") + invalidFile := filepath.Join(t.TempDir(), "invalid1.keytab") _, clean, err := utils.NewMockKeytab( utils.WithPrincipal("HTTP/sso.example.com"), utils.WithRealm("TEST.LOCAL"), utils.WithPairs(utils.EncryptTypePair{ Version: 2, EncryptType: 18, CreateTime: tm, }), - utils.WithFilename("./temp.keytab"), + utils.WithFilename(tempFile), ) require.NoError(t, err) t.Cleanup(clean) - err = os.WriteFile("./invalid1.keytab", []byte("12345"), 0600) + err = os.WriteFile(invalidFile, []byte("12345"), 0o600) require.NoError(t, err) - t.Cleanup(func() { - os.Remove("./invalid1.keytab") - }) - fn, err := NewKeytabFileLookupFunc("./temp.keytab", "./invalid1.keytab") + fn, err := NewKeytabFileLookupFunc(tempFile, invalidFile)v3/spnego/utils/mock_keytab.go (2)
135-135: Consider more restrictive file permissions for keytab files.File permissions
0o666(read/write for all users) are permissive for keytab files, which contain sensitive Kerberos credentials. Even in test scenarios, using more restrictive permissions like0o600(read/write for owner only) would better align with security best practices.Apply this diff to use more restrictive permissions:
- file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666) + file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
139-141: Consider handling cleanup errors to avoid test pollution.The cleanup function silently ignores errors when removing the keytab file. While not critical, this could lead to test pollution if files accumulate when removal fails.
Consider logging or returning cleanup errors if your test framework supports cleanup error handling:
clean = func() { - _ = defaultFileOperator.Remove(opt.Filename) + if err := defaultFileOperator.Remove(opt.Filename); err != nil { + // Consider logging: fmt.Fprintf(os.Stderr, "cleanup warning: %v\n", err) + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
go.workis excluded by!**/*.work,!**/*.workv3/spnego/go.modis excluded by!**/*.modv3/spnego/go.sumis excluded by!**/*.sum,!**/*.sum
📒 Files selected for processing (17)
v3/spnego/README.md(1 hunks)v3/spnego/README.zh-CN.md(1 hunks)v3/spnego/config.go(1 hunks)v3/spnego/config_test.go(1 hunks)v3/spnego/doc.go(1 hunks)v3/spnego/error.go(1 hunks)v3/spnego/example/example.go(1 hunks)v3/spnego/identity.go(1 hunks)v3/spnego/identity_test.go(1 hunks)v3/spnego/spnego.go(1 hunks)v3/spnego/spnego_test.go(1 hunks)v3/spnego/utils/adapter.go(1 hunks)v3/spnego/utils/adapter_test.go(1 hunks)v3/spnego/utils/keytab.go(1 hunks)v3/spnego/utils/keytab_test.go(1 hunks)v3/spnego/utils/mock_keytab.go(1 hunks)v3/spnego/utils/mock_keytab_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (19)
📚 Learning: 2024-11-08T04:10:42.990Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/cache/cache_test.go:897-897
Timestamp: 2024-11-08T04:10:42.990Z
Learning: In the Fiber framework, `Context()` is being renamed to `RequestCtx()`, and `UserContext()` to `Context()` to improve clarity and align with Go's context conventions.
Applied to files:
v3/spnego/identity.gov3/spnego/utils/adapter.go
📚 Learning: 2025-02-12T11:24:31.153Z
Learnt from: ReneWerner87
Repo: gofiber/storage PR: 0
File: :0-0
Timestamp: 2025-02-12T11:24:31.153Z
Learning: The storage package in gofiber/storage repository can be used independently of the Fiber web framework.
Applied to files:
v3/spnego/identity.go
📚 Learning: 2025-10-16T07:15:26.529Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:15:26.529Z
Learning: In Fiber v3, net/http handlers (http.Handler, http.HandlerFunc, or raw func(http.ResponseWriter, *http.Request)) can be passed directly to routing methods like app.Get(), app.Post(), etc. The framework automatically detects and wraps them internally via toFiberHandler/collectHandlers. The github.com/gofiber/fiber/v3/middleware/adaptor package is legacy and should not be suggested for tests or code using native net/http handler support.
Applied to files:
v3/spnego/spnego.gov3/spnego/utils/adapter_test.gov3/spnego/utils/adapter.go
📚 Learning: 2024-11-23T19:50:41.765Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.
Applied to files:
v3/spnego/example/example.gov3/spnego/utils/adapter_test.gov3/spnego/identity_test.gov3/spnego/utils/adapter.go
📚 Learning: 2024-11-23T19:35:36.767Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/main.go:0-0
Timestamp: 2024-11-23T19:35:36.767Z
Learning: In the Go `clean-code` example (`clean-code/app/main.go`) in the `gofiber/recipes` repository, it's acceptable to omit graceful shutdown handling, as the example code prioritizes simplicity over production-level practices.
Applied to files:
v3/spnego/example/example.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.
Applied to files:
v3/spnego/example/example.gov3/spnego/spnego_test.gov3/spnego/utils/adapter_test.gov3/spnego/identity_test.gov3/spnego/utils/adapter.go
📚 Learning: 2024-07-02T13:29:56.992Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-07-02T13:29:56.992Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.
Applied to files:
v3/spnego/config_test.gov3/spnego/spnego_test.gov3/spnego/utils/mock_keytab_test.go
📚 Learning: 2024-12-13T08:14:22.851Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.
Applied to files:
v3/spnego/config_test.gov3/spnego/identity_test.gov3/spnego/utils/mock_keytab_test.go
📚 Learning: 2024-11-23T19:50:06.387Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/datasources/database/db_mock.go:13-19
Timestamp: 2024-11-23T19:50:06.387Z
Learning: In test code within `clean-code/app/datasources/database/db_mock.go`, adding safety checks like context validation, safe type assertions, and extra documentation is not necessary.
Applied to files:
v3/spnego/config_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.
Applied to files:
v3/spnego/spnego_test.gov3/spnego/identity_test.go
📚 Learning: 2024-09-25T17:09:03.756Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/middleware_test.go:400-407
Timestamp: 2024-09-25T17:09:03.756Z
Learning: In the `Test_Session_Next` function in `middleware/session/middleware_test.go`, the variable `doNext` is properly synchronized with the `muNext` mutex for both read and write access.
Applied to files:
v3/spnego/spnego_test.go
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.
Applied to files:
v3/spnego/utils/adapter_test.gov3/spnego/identity_test.gov3/spnego/utils/adapter.go
📚 Learning: 2024-11-15T07:56:21.623Z
Learnt from: ReneWerner87
Repo: gofiber/fiber PR: 3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.
Applied to files:
v3/spnego/utils/adapter_test.gov3/spnego/identity_test.gov3/spnego/utils/adapter.go
📚 Learning: 2024-11-21T11:28:14.554Z
Learnt from: ReneWerner87
Repo: gofiber/contrib PR: 0
File: :0-0
Timestamp: 2024-11-21T11:28:14.554Z
Learning: Using `fiber.Ctx`, even when wrapped, does not achieve version independence, as it still relies on the specific Fiber implementation.
Applied to files:
v3/spnego/utils/adapter_test.gov3/spnego/utils/adapter.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.
Applied to files:
v3/spnego/utils/adapter_test.gov3/spnego/identity_test.gov3/spnego/utils/adapter.go
📚 Learning: 2024-11-29T15:24:06.083Z
Learnt from: ReneWerner87
Repo: gofiber/contrib PR: 0
File: :0-0
Timestamp: 2024-11-29T15:24:06.083Z
Learning: When customizing the Fiber context to achieve version independence, using generics in methods like `Status` and `Type` allows for fluent method chaining. Implementing a generic interface for `Ctx` on the `App` prevents class switching when registering custom contexts that use fluent methods. Providing multiple `New` methods allows users who do not wish to customize the context to continue using `fiber.New` without changes.
Applied to files:
v3/spnego/utils/adapter_test.gov3/spnego/utils/adapter.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests in this project use testify require.
Applied to files:
v3/spnego/utils/adapter_test.go
📚 Learning: 2024-12-01T01:15:48.126Z
Learnt from: juls0730
Repo: gofiber/recipes PR: 2710
File: tableflip/main.go:61-62
Timestamp: 2024-12-01T01:15:48.126Z
Learning: In the GoFiber `tableflip` recipe (`tableflip/main.go`), the implementation matches the upstream reference implementation. Future code suggestions should consider maintaining this alignment to ensure consistency.
Applied to files:
v3/spnego/identity_test.go
📚 Learning: 2024-11-29T12:37:27.581Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
Applied to files:
v3/spnego/identity_test.go
🧬 Code graph analysis (12)
v3/spnego/identity.go (1)
v3/spnego/utils/adapter.go (1)
FiberContext(12-19)
v3/spnego/spnego.go (4)
v3/spnego/config.go (1)
Config(21-26)v3/spnego/error.go (3)
ErrConfigInvalidOfKeytabLookupFunctionRequired(6-6)ErrLookupKeytabFailed(9-9)ErrConvertRequestFailed(12-12)v3/spnego/identity.go (1)
SetAuthenticatedIdentityToContext(12-14)v3/spnego/utils/adapter.go (1)
NewWrapFiberContext(53-57)
v3/spnego/example/example.go (5)
v3/spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-151)WithPrincipal(32-36)WithRealm(40-44)WithFilename(49-53)WithPairs(58-62)v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)v3/spnego/config.go (2)
NewKeytabFileLookupFunc(30-45)Config(21-26)v3/spnego/spnego.go (1)
NewSpnegoKrb5AuthenticateMiddleware(21-57)v3/spnego/identity.go (1)
GetAuthenticatedIdentityFromContext(25-28)
v3/spnego/config.go (1)
v3/spnego/error.go (2)
ErrConfigInvalidOfAtLeastOneKeytabFileRequired(15-15)ErrLoadKeytabFileFailed(18-18)
v3/spnego/utils/keytab_test.go (2)
v3/spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-151)WithRealm(40-44)WithPrincipal(32-36)WithPassword(67-71)WithPairs(58-62)v3/spnego/utils/keytab.go (2)
EncryptTypePair(22-26)GetKeytabInfo(50-77)
v3/spnego/utils/mock_keytab.go (1)
v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)
v3/spnego/config_test.go (4)
v3/spnego/config.go (1)
NewKeytabFileLookupFunc(30-45)v3/spnego/error.go (2)
ErrConfigInvalidOfAtLeastOneKeytabFileRequired(15-15)ErrLoadKeytabFileFailed(18-18)v3/spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-151)WithPrincipal(32-36)WithRealm(40-44)WithPairs(58-62)WithFilename(49-53)v3/spnego/utils/keytab.go (2)
EncryptTypePair(22-26)GetKeytabInfo(50-77)
v3/spnego/spnego_test.go (6)
v3/spnego/spnego.go (1)
NewSpnegoKrb5AuthenticateMiddleware(21-57)v3/spnego/config.go (2)
Config(21-26)NewKeytabFileLookupFunc(30-45)v3/spnego/error.go (2)
ErrConfigInvalidOfKeytabLookupFunctionRequired(6-6)ErrLookupKeytabFailed(9-9)v3/spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-151)WithPrincipal(32-36)WithRealm(40-44)WithFilename(49-53)WithPairs(58-62)v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)v3/spnego/identity.go (1)
GetAuthenticatedIdentityFromContext(25-28)
v3/spnego/utils/adapter_test.go (1)
v3/spnego/utils/adapter.go (1)
NewWrapFiberContext(53-57)
v3/spnego/identity_test.go (1)
v3/spnego/identity.go (2)
SetAuthenticatedIdentityToContext(12-14)GetAuthenticatedIdentityFromContext(25-28)
v3/spnego/utils/mock_keytab_test.go (2)
v3/spnego/utils/mock_keytab.go (6)
NewMockKeytab(123-151)WithPrincipal(32-36)WithRealm(40-44)WithPairs(58-62)WithFilename(49-53)WithPassword(67-71)v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)
v3/spnego/utils/adapter.go (1)
v3/spnego/identity.go (1)
FiberContext(5-7)
🪛 markdownlint-cli2 (0.18.1)
v3/spnego/README.md
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
47-47: Hard tabs
Column: 1
(MD010, no-hard-tabs)
48-48: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
61-61: Hard tabs
Column: 1
(MD010, no-hard-tabs)
62-62: Hard tabs
Column: 1
(MD010, no-hard-tabs)
63-63: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
65-65: Hard tabs
Column: 1
(MD010, no-hard-tabs)
66-66: Hard tabs
Column: 1
(MD010, no-hard-tabs)
67-67: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
74-74: Hard tabs
Column: 1
(MD010, no-hard-tabs)
75-75: Hard tabs
Column: 1
(MD010, no-hard-tabs)
76-76: Hard tabs
Column: 1
(MD010, no-hard-tabs)
77-77: Hard tabs
Column: 1
(MD010, no-hard-tabs)
78-78: Hard tabs
Column: 1
(MD010, no-hard-tabs)
79-79: Hard tabs
Column: 1
(MD010, no-hard-tabs)
80-80: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
83-83: Hard tabs
Column: 1
(MD010, no-hard-tabs)
84-84: Hard tabs
Column: 1
(MD010, no-hard-tabs)
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
88-88: Hard tabs
Column: 1
(MD010, no-hard-tabs)
89-89: Hard tabs
Column: 1
(MD010, no-hard-tabs)
90-90: Hard tabs
Column: 1
(MD010, no-hard-tabs)
91-91: Hard tabs
Column: 1
(MD010, no-hard-tabs)
92-92: Hard tabs
Column: 1
(MD010, no-hard-tabs)
93-93: Hard tabs
Column: 1
(MD010, no-hard-tabs)
95-95: Hard tabs
Column: 1
(MD010, no-hard-tabs)
96-96: Hard tabs
Column: 1
(MD010, no-hard-tabs)
v3/spnego/README.zh-CN.md
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
47-47: Hard tabs
Column: 1
(MD010, no-hard-tabs)
48-48: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
61-61: Hard tabs
Column: 1
(MD010, no-hard-tabs)
62-62: Hard tabs
Column: 1
(MD010, no-hard-tabs)
63-63: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
65-65: Hard tabs
Column: 1
(MD010, no-hard-tabs)
66-66: Hard tabs
Column: 1
(MD010, no-hard-tabs)
67-67: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
74-74: Hard tabs
Column: 1
(MD010, no-hard-tabs)
75-75: Hard tabs
Column: 1
(MD010, no-hard-tabs)
76-76: Hard tabs
Column: 1
(MD010, no-hard-tabs)
77-77: Hard tabs
Column: 1
(MD010, no-hard-tabs)
78-78: Hard tabs
Column: 1
(MD010, no-hard-tabs)
79-79: Hard tabs
Column: 1
(MD010, no-hard-tabs)
80-80: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
83-83: Hard tabs
Column: 1
(MD010, no-hard-tabs)
84-84: Hard tabs
Column: 1
(MD010, no-hard-tabs)
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
88-88: Hard tabs
Column: 1
(MD010, no-hard-tabs)
89-89: Hard tabs
Column: 1
(MD010, no-hard-tabs)
90-90: Hard tabs
Column: 1
(MD010, no-hard-tabs)
91-91: Hard tabs
Column: 1
(MD010, no-hard-tabs)
92-92: Hard tabs
Column: 1
(MD010, no-hard-tabs)
93-93: Hard tabs
Column: 1
(MD010, no-hard-tabs)
95-95: Hard tabs
Column: 1
(MD010, no-hard-tabs)
96-96: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🔇 Additional comments (21)
v3/spnego/doc.go (1)
1-4: LGTM!Package documentation is clear and follows Go conventions.
v3/spnego/utils/mock_keytab_test.go (1)
36-194: LGTM!Comprehensive test coverage for mock keytab utilities with proper error path testing, cleanup, and use of testify assertions per project conventions.
v3/spnego/utils/keytab_test.go (1)
10-46: LGTM!Well-structured test that thoroughly validates keytab information extraction, including multi-entry scenarios and timestamp handling.
v3/spnego/spnego.go (1)
34-39: Verify performance implications of per-request keytab lookup.The keytab is looked up on every request (line 36). While the PR description mentions "dynamic keytab loading" as a feature (for secrets rotation, etc.), this could introduce latency if the lookup is expensive.
Consider:
- If keytab lookup involves I/O (file reads, database queries, secrets manager API calls), document the performance characteristics
- For high-traffic scenarios, evaluate whether caching with TTL-based invalidation would be appropriate
- Provide guidance in documentation about the performance trade-offs of dynamic vs. cached keytab loading
You may want to add a note in the README about the per-request lookup behavior and when users should implement their own caching layer in the
KeytabLookupFunc.v3/spnego/config.go (2)
13-17: LGTM!The
KeytabLookupFunctype definition is clean and well-documented, providing good extensibility for various keytab sources.
28-45: LGTM!The
NewKeytabFileLookupFuncimplementation correctly validates input, loads keytab files, and merges entries with proper error handling and wrapping.v3/spnego/config_test.go (2)
12-16: LGTM!The test correctly validates that
NewKeytabFileLookupFuncrequires at least one keytab file.
80-131: LGTM!This test properly uses
t.TempDir()and thoroughly validates the keytab merging functionality with appropriate assertions.v3/spnego/identity.go (2)
5-7: LGTM!The
FiberContextinterface provides a clean abstraction for context operations, enabling flexible usage across different Fiber context types.
9-28: LGTM!The identity helper functions are well-implemented with proper type assertions, clear documentation, and usage examples. The generic approach provides good flexibility.
v3/spnego/spnego_test.go (3)
18-22: LGTM!The test correctly validates that
NewSpnegoKrb5AuthenticateMiddlewarerequires a KeytabLookup function.
23-41: LGTM!The test properly validates error handling when keytab lookup fails, ensuring the middleware returns a 500 status with the appropriate error message.
42-90: LGTM!The test comprehensively validates the authentication flow with multiple keytabs, properly uses
t.TempDir(), and correctly expects a 401 status for unauthenticated requests. The identity logging in the handler is appropriate.v3/spnego/error.go (1)
1-18: LGTM!The sentinel error definitions are clear, well-documented, and follow Go best practices for error handling.
v3/spnego/utils/keytab.go (3)
12-30: LGTM!The type definitions are clear, well-documented, and appropriately structured for representing keytab information.
32-49: LGTM!Excellent documentation with clear parameter descriptions and a practical usage example.
50-68: LGTM!The accumulation logic correctly builds a map of keytab information grouped by principal, with proper handling of new and existing entries.
v3/spnego/utils/mock_keytab.go (4)
10-28: LGTM! Clean functional options pattern.The
mockOptionsstruct andapplymethod correctly implement the functional options pattern for flexible keytab configuration.
30-75: LGTM! Well-designed option functions.The option functions follow Go idioms correctly and include helpful documentation with examples.
77-102: LGTM! Appropriate test abstractions.The default options and file operator abstraction are well-designed for testing scenarios.
124-132: LGTM! Correct keytab entry creation.The entry addition logic correctly handles errors and provides informative error messages.
| ```go | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "time" | ||
|
|
||
| "github.com/gofiber/contrib/v3/spnego" | ||
| "github.com/gofiber/contrib/v3/spnego/utils" | ||
| "github.com/gofiber/fiber/v3" | ||
| "github.com/gofiber/fiber/v3/log" | ||
| ) | ||
|
|
||
| func main() { | ||
| app := fiber.New() | ||
| // Create a configuration with a keytab lookup function | ||
| // For testing, you can create a mock keytab file using utils.NewMockKeytab | ||
| // In production, use a real keytab file | ||
| _, clean, err := utils.NewMockKeytab( | ||
| utils.WithPrincipal("HTTP/sso1.example.com"), | ||
| utils.WithRealm("EXAMPLE.LOCAL"), | ||
| utils.WithFilename("./temp-sso1.keytab"), | ||
| utils.WithPairs(utils.EncryptTypePair{ | ||
| Version: 2, | ||
| EncryptType: 18, | ||
| CreateTime: time.Now(), | ||
| }), | ||
| ) | ||
| if err != nil { | ||
| log.Fatalf("Failed to create mock keytab: %v", err) | ||
| } | ||
| defer clean() | ||
| keytabLookup, err := spnego.NewKeytabFileLookupFunc("./temp-sso1.keytab") | ||
| if err != nil { | ||
| log.Fatalf("Failed to create keytab lookup function: %v", err) | ||
| } | ||
|
|
||
| // Create the middleware | ||
| authMiddleware, err := spnego.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{ | ||
| KeytabLookup: keytabLookup, | ||
| }) | ||
| if err != nil { | ||
| log.Fatalf("Failed to create middleware: %v", err) | ||
| } | ||
|
|
||
| // Apply the middleware to protected routes | ||
| app.Use("/protected", authMiddleware) | ||
|
|
||
| // Access authenticated identity | ||
| app.Get("/protected/resource", func(c fiber.Ctx) error { | ||
| identity, ok := spnego.GetAuthenticatedIdentityFromContext(c) | ||
| if !ok { | ||
| return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized") | ||
| } | ||
| return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName())) | ||
| }) | ||
|
|
||
| log.Info("Server is running on :3000") | ||
| app.Listen(":3000") | ||
| } | ||
| ``` |
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.
Replace hard tabs with spaces in code block.
The markdown linter flags hard tabs throughout the usage example. Replace tabs with spaces for consistent rendering across different editors and viewers.
As per static analysis tools.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
47-47: Hard tabs
Column: 1
(MD010, no-hard-tabs)
48-48: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
61-61: Hard tabs
Column: 1
(MD010, no-hard-tabs)
62-62: Hard tabs
Column: 1
(MD010, no-hard-tabs)
63-63: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
65-65: Hard tabs
Column: 1
(MD010, no-hard-tabs)
66-66: Hard tabs
Column: 1
(MD010, no-hard-tabs)
67-67: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
74-74: Hard tabs
Column: 1
(MD010, no-hard-tabs)
75-75: Hard tabs
Column: 1
(MD010, no-hard-tabs)
76-76: Hard tabs
Column: 1
(MD010, no-hard-tabs)
77-77: Hard tabs
Column: 1
(MD010, no-hard-tabs)
78-78: Hard tabs
Column: 1
(MD010, no-hard-tabs)
79-79: Hard tabs
Column: 1
(MD010, no-hard-tabs)
80-80: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
83-83: Hard tabs
Column: 1
(MD010, no-hard-tabs)
84-84: Hard tabs
Column: 1
(MD010, no-hard-tabs)
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
88-88: Hard tabs
Column: 1
(MD010, no-hard-tabs)
89-89: Hard tabs
Column: 1
(MD010, no-hard-tabs)
90-90: Hard tabs
Column: 1
(MD010, no-hard-tabs)
91-91: Hard tabs
Column: 1
(MD010, no-hard-tabs)
92-92: Hard tabs
Column: 1
(MD010, no-hard-tabs)
93-93: Hard tabs
Column: 1
(MD010, no-hard-tabs)
95-95: Hard tabs
Column: 1
(MD010, no-hard-tabs)
96-96: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🤖 Prompt for AI Agents
In v3/spnego/README.md around lines 38 to 98, the Go usage example contains hard
tab characters; replace every hard tab in the fenced code block with spaces (use
a consistent indentation width such as 2 or 4 spaces across the file), ensuring
leading indentation and inline alignment are preserved, then save the file and
run the markdown linter to confirm tabs are 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.
Pull Request Overview
This PR introduces enterprise-grade SPNEGO authentication middleware for Fiber v3, enabling Kerberos-based single sign-on (SSO) capabilities. The implementation follows Fiber's middleware patterns with dynamic keytab loading and comprehensive test coverage.
Key changes:
- Complete SPNEGO/Kerberos authentication middleware implementation with RFC compliance
- Flexible keytab lookup system supporting multiple sources (files, databases, remote services)
- Full test suite with >87% coverage and testing utilities for mock keytab generation
Reviewed Changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| v3/spnego/go.mod | Module definition with dependencies for SPNEGO middleware |
| v3/spnego/spnego.go | Core middleware implementation handling SPNEGO authentication |
| v3/spnego/config.go | Configuration structures and keytab file lookup functions |
| v3/spnego/identity.go | Identity management utilities for storing/retrieving authenticated users |
| v3/spnego/error.go | Error definitions for middleware operations |
| v3/spnego/doc.go | Package documentation |
| v3/spnego/utils/adapter.go | HTTP ResponseWriter adapter for Fiber contexts |
| v3/spnego/utils/keytab.go | Keytab information extraction utilities |
| v3/spnego/utils/mock_keytab.go | Test utility for creating mock keytab files |
| v3/spnego/example/example.go | Usage example demonstrating middleware setup |
| v3/spnego/README.md | English documentation with API reference and usage examples |
| v3/spnego/README.zh-CN.md | Chinese documentation with API reference and usage examples |
| v3/spnego/*_test.go | Comprehensive test suite for all components |
| go.work | Workspace update to include new spnego module |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1. add file close error handing in utils/mock_keytab.go 2. format go file 3. Updated example comments in example.go with additional usage instructions
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.
Actionable comments posted: 2
♻️ Duplicate comments (2)
v3/spnego/README.md (1)
38-98: Replace hard tabs with spaces in code block.The markdown linter flags hard tabs throughout the usage example. Replace tabs with spaces for consistent rendering across different editors and viewers.
As per static analysis tools.
v3/spnego/example/example.go (1)
67-71: Remove the Output directive to prevent test failures.The
// Output:directive causesgo testto execute this example and compare stdout, but:
- Line 52's
log.Infomay not write to stdout in a capturable way- Lines 55-56, 58's
fmt.Printlnstatements will produce output not matching the expected output- Line 63 binds to
sso.example.local:3000which can fail DNS resolution in CIRemove the Output directive so the example compiles but doesn't execute during tests:
- // Output: - // Server is running on :3000 - // use curl -kv --negotiate http://sso.example.local:3000/protected/resource - // if response is 401, execute `klist` to check use kerberos session - // close server
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
v3/spnego/README.md(1 hunks)v3/spnego/README.zh-CN.md(1 hunks)v3/spnego/config.go(1 hunks)v3/spnego/example/example.go(1 hunks)v3/spnego/spnego.go(1 hunks)v3/spnego/utils/keytab.go(1 hunks)v3/spnego/utils/mock_keytab.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- v3/spnego/config.go
- v3/spnego/spnego.go
- v3/spnego/utils/keytab.go
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2024-11-23T19:50:41.765Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.
Applied to files:
v3/spnego/example/example.go
📚 Learning: 2024-11-23T19:35:36.767Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/main.go:0-0
Timestamp: 2024-11-23T19:35:36.767Z
Learning: In the Go `clean-code` example (`clean-code/app/main.go`) in the `gofiber/recipes` repository, it's acceptable to omit graceful shutdown handling, as the example code prioritizes simplicity over production-level practices.
Applied to files:
v3/spnego/example/example.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.
Applied to files:
v3/spnego/example/example.go
📚 Learning: 2024-11-23T19:51:05.812Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:18-20
Timestamp: 2024-11-23T19:51:05.812Z
Learning: In the `clean-code/app/server/server.go` file, the team prefers to maintain a simple health check endpoint without additional system status information.
Applied to files:
v3/spnego/example/example.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: hcancelik
Repo: gofiber/fiber PR: 3036
File: docs/middleware/cache.md:103-103
Timestamp: 2024-10-08T19:06:06.583Z
Learning: There are no hard tabs in the lines 100 to 105 of the `docs/middleware/cache.md` file. Future comments about formatting should accurately reflect the actual content.
Applied to files:
v3/spnego/README.md
🧬 Code graph analysis (2)
v3/spnego/example/example.go (5)
v3/spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-154)WithPrincipal(32-36)WithRealm(40-44)WithFilename(49-53)WithPairs(58-62)v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)v3/spnego/config.go (2)
NewKeytabFileLookupFunc(32-47)Config(23-28)v3/spnego/spnego.go (1)
NewSpnegoKrb5AuthenticateMiddleware(21-57)v3/spnego/identity.go (1)
GetAuthenticatedIdentityFromContext(25-28)
v3/spnego/utils/mock_keytab.go (1)
v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)
🪛 markdownlint-cli2 (0.18.1)
v3/spnego/README.md
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
47-47: Hard tabs
Column: 1
(MD010, no-hard-tabs)
48-48: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
61-61: Hard tabs
Column: 1
(MD010, no-hard-tabs)
62-62: Hard tabs
Column: 1
(MD010, no-hard-tabs)
63-63: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
65-65: Hard tabs
Column: 1
(MD010, no-hard-tabs)
66-66: Hard tabs
Column: 1
(MD010, no-hard-tabs)
67-67: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
74-74: Hard tabs
Column: 1
(MD010, no-hard-tabs)
75-75: Hard tabs
Column: 1
(MD010, no-hard-tabs)
76-76: Hard tabs
Column: 1
(MD010, no-hard-tabs)
77-77: Hard tabs
Column: 1
(MD010, no-hard-tabs)
78-78: Hard tabs
Column: 1
(MD010, no-hard-tabs)
79-79: Hard tabs
Column: 1
(MD010, no-hard-tabs)
80-80: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
83-83: Hard tabs
Column: 1
(MD010, no-hard-tabs)
84-84: Hard tabs
Column: 1
(MD010, no-hard-tabs)
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
88-88: Hard tabs
Column: 1
(MD010, no-hard-tabs)
89-89: Hard tabs
Column: 1
(MD010, no-hard-tabs)
90-90: Hard tabs
Column: 1
(MD010, no-hard-tabs)
91-91: Hard tabs
Column: 1
(MD010, no-hard-tabs)
92-92: Hard tabs
Column: 1
(MD010, no-hard-tabs)
93-93: Hard tabs
Column: 1
(MD010, no-hard-tabs)
95-95: Hard tabs
Column: 1
(MD010, no-hard-tabs)
96-96: Hard tabs
Column: 1
(MD010, no-hard-tabs)
v3/spnego/README.zh-CN.md
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
47-47: Hard tabs
Column: 1
(MD010, no-hard-tabs)
48-48: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
61-61: Hard tabs
Column: 1
(MD010, no-hard-tabs)
62-62: Hard tabs
Column: 1
(MD010, no-hard-tabs)
63-63: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
65-65: Hard tabs
Column: 1
(MD010, no-hard-tabs)
66-66: Hard tabs
Column: 1
(MD010, no-hard-tabs)
67-67: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
74-74: Hard tabs
Column: 1
(MD010, no-hard-tabs)
75-75: Hard tabs
Column: 1
(MD010, no-hard-tabs)
76-76: Hard tabs
Column: 1
(MD010, no-hard-tabs)
77-77: Hard tabs
Column: 1
(MD010, no-hard-tabs)
78-78: Hard tabs
Column: 1
(MD010, no-hard-tabs)
79-79: Hard tabs
Column: 1
(MD010, no-hard-tabs)
80-80: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
83-83: Hard tabs
Column: 1
(MD010, no-hard-tabs)
84-84: Hard tabs
Column: 1
(MD010, no-hard-tabs)
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
88-88: Hard tabs
Column: 1
(MD010, no-hard-tabs)
89-89: Hard tabs
Column: 1
(MD010, no-hard-tabs)
90-90: Hard tabs
Column: 1
(MD010, no-hard-tabs)
91-91: Hard tabs
Column: 1
(MD010, no-hard-tabs)
92-92: Hard tabs
Column: 1
(MD010, no-hard-tabs)
93-93: Hard tabs
Column: 1
(MD010, no-hard-tabs)
95-95: Hard tabs
Column: 1
(MD010, no-hard-tabs)
96-96: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🔇 Additional comments (1)
v3/spnego/utils/mock_keytab.go (1)
123-154: LGTM!The error handling for
file.Close()at line 147 is now properly implemented, addressing the previous review concern. The mock keytab creation logic, option pattern, and file operations are all well-structured.
…in example.go This commit: - Removed Chinese documentation (README.zh-CN.md) - Added comprehensive instructions in example.go
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.
Actionable comments posted: 0
♻️ Duplicate comments (1)
v3/spnego/README.md (1)
39-96: Replace hard tabs with spaces in the Go code example. This issue was flagged in the previous review and remains unresolved. All indentation in the fenced code block must use spaces for consistent rendering across editors and to comply with the markdown linter.Apply this diff to replace hard tabs with 4-space indentation:
package main import ( - "fmt" - "time" - - "github.com/gofiber/contrib/v3/spnego" - "github.com/gofiber/contrib/v3/spnego/utils" - "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/log" + "fmt" + "time" + + "github.com/gofiber/contrib/v3/spnego" + "github.com/gofiber/contrib/v3/spnego/utils" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/log" ) func main() { - app := fiber.New() - // Create a configuration with a keytab lookup function - // For testing, you can create a mock keytab file using utils.NewMockKeytab - // In production, use a real keytab file - _, clean, err := utils.NewMockKeytab( - utils.WithPrincipal("HTTP/sso1.example.com"), - utils.WithRealm("EXAMPLE.LOCAL"), - utils.WithFilename("./temp-sso1.keytab"), - utils.WithPairs(utils.EncryptTypePair{ - Version: 2, - EncryptType: 18, - CreateTime: time.Now(), - }), - ) - if err != nil { - log.Fatalf("Failed to create mock keytab: %v", err) - } - defer clean() - keytabLookup, err := spnego.NewKeytabFileLookupFunc("./temp-sso1.keytab") - if err != nil { - log.Fatalf("Failed to create keytab lookup function: %v", err) - } - - // Create the middleware - authMiddleware, err := spnego.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{ - KeytabLookup: keytabLookup, - }) - if err != nil { - log.Fatalf("Failed to create middleware: %v", err) - } - - // Apply the middleware to protected routes - app.Use("/protected", authMiddleware) - - // Access authenticated identity - app.Get("/protected/resource", func(c fiber.Ctx) error { - identity, ok := spnego.GetAuthenticatedIdentityFromContext(c) - if !ok { - return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized") - } - return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName())) - }) - - log.Info("Server is running on :3000") - app.Listen(":3000") + app := fiber.New() + // Create a configuration with a keytab lookup function + // For testing, you can create a mock keytab file using utils.NewMockKeytab + // In production, use a real keytab file + _, clean, err := utils.NewMockKeytab( + utils.WithPrincipal("HTTP/sso1.example.com"), + utils.WithRealm("EXAMPLE.LOCAL"), + utils.WithFilename("./temp-sso1.keytab"), + utils.WithPairs(utils.EncryptTypePair{ + Version: 2, + EncryptType: 18, + CreateTime: time.Now(), + }), + ) + if err != nil { + log.Fatalf("Failed to create mock keytab: %v", err) + } + defer clean() + keytabLookup, err := spnego.NewKeytabFileLookupFunc("./temp-sso1.keytab") + if err != nil { + log.Fatalf("Failed to create keytab lookup function: %v", err) + } + + // Create the middleware + authMiddleware, err := spnego.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{ + KeytabLookup: keytabLookup, + }) + if err != nil { + log.Fatalf("Failed to create middleware: %v", err) + } + + // Apply the middleware to protected routes + app.Use("/protected", authMiddleware) + + // Access authenticated identity + app.Get("/protected/resource", func(c fiber.Ctx) error { + identity, ok := spnego.GetAuthenticatedIdentityFromContext(c) + if !ok { + return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized") + } + return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName())) + }) + + log.Info("Server is running on :3000") + app.Listen(":3000") }
🧹 Nitpick comments (1)
v3/spnego/README.md (1)
139-142: Add explicit Go version requirement to the Requirements section. Currently missing the minimum Go version. Based on the codebase patterns, add a requirement for Go 1.22 or higher.Apply this diff to add the Go version requirement:
## Requirements +- Go 1.22 or higher - Fiber v3 - Kerberos infrastructure
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
v3/spnego/README.md(1 hunks)v3/spnego/example/example.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- v3/spnego/example/example.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: hcancelik
Repo: gofiber/fiber PR: 3036
File: docs/middleware/cache.md:103-103
Timestamp: 2024-10-08T19:06:06.583Z
Learning: There are no hard tabs in the lines 100 to 105 of the `docs/middleware/cache.md` file. Future comments about formatting should accurately reflect the actual content.
Applied to files:
v3/spnego/README.md
📚 Learning: 2025-11-13T11:34:38.503Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3824
File: listen_test.go:560-564
Timestamp: 2025-11-13T11:34:38.503Z
Learning: The gofiber/fiber project only supports the latest Go versions, so Go 1.22+ syntax features (such as `for range N` integer range loops) are acceptable and preferred.
Applied to files:
v3/spnego/README.md
🪛 markdownlint-cli2 (0.18.1)
v3/spnego/README.md
40-40: Hard tabs
Column: 1
(MD010, no-hard-tabs)
41-41: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
44-44: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
50-50: Hard tabs
Column: 1
(MD010, no-hard-tabs)
51-51: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
61-61: Hard tabs
Column: 1
(MD010, no-hard-tabs)
62-62: Hard tabs
Column: 1
(MD010, no-hard-tabs)
63-63: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
65-65: Hard tabs
Column: 1
(MD010, no-hard-tabs)
66-66: Hard tabs
Column: 1
(MD010, no-hard-tabs)
67-67: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
74-74: Hard tabs
Column: 1
(MD010, no-hard-tabs)
75-75: Hard tabs
Column: 1
(MD010, no-hard-tabs)
76-76: Hard tabs
Column: 1
(MD010, no-hard-tabs)
77-77: Hard tabs
Column: 1
(MD010, no-hard-tabs)
78-78: Hard tabs
Column: 1
(MD010, no-hard-tabs)
79-79: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
82-82: Hard tabs
Column: 1
(MD010, no-hard-tabs)
84-84: Hard tabs
Column: 1
(MD010, no-hard-tabs)
85-85: Hard tabs
Column: 1
(MD010, no-hard-tabs)
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
88-88: Hard tabs
Column: 1
(MD010, no-hard-tabs)
89-89: Hard tabs
Column: 1
(MD010, no-hard-tabs)
90-90: Hard tabs
Column: 1
(MD010, no-hard-tabs)
91-91: Hard tabs
Column: 1
(MD010, no-hard-tabs)
93-93: Hard tabs
Column: 1
(MD010, no-hard-tabs)
94-94: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🔇 Additional comments (3)
v3/spnego/README.md (3)
1-20: Documentation structure is well-organized. Header metadata, feature list, and version compatibility are clear and properly communicate the middleware's scope. Version explicitly limited to v3 aligns with PR objectives.
27-32: Installation instructions are clear and accurate. The Go module path is correct for v3.
98-147: API documentation and examples are clear and well-structured. The Dynamic Keytab Lookup section effectively demonstrates extensibility, API reference is concise, and configuration section is helpful. Operational notes provide useful guidance.
|
@jarod2011 Few small things:
|
…lify context wrapping - Remove dependency on utils adapter package by directly implementing wrapContext struct in spnego.go - Implement wrapContext struct with methods to adapt Fiber context to http.ResponseWriter interface - add test-spnego.yaml workflows - add error check in mock keytab close file - rename creating function to New
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.
Actionable comments posted: 1
♻️ Duplicate comments (3)
v3/spnego/README.md (1)
36-96: Replace hard tabs with spaces in code blocks.The markdown linter flags hard tabs throughout the usage example (lines 40-96). Replace all tabs with spaces (2 or 4 spaces consistently) for proper rendering across editors and viewers.
As per static analysis tools.
v3/spnego/spnego.go (1)
37-37: Simplify error wrapping to avoid double %w.Using
%wtwice in the same format string can make error chains unclear. Use%vfor the inner error to maintain a clear error chain.Apply this diff:
- return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err) + return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err)Wait, the code already uses
%vfor the inner error. Let me check line 50.v3/spnego/example/example.go (1)
13-67: Example function will fail or hang duringgo test.The
ExampleNew()function name follows Go's testable example convention, which meansgo testwill execute it. However:
- It starts a real server and attempts to bind to
sso.example.local:3000(DNS resolution may fail in CI)- The goroutine prints multiple guidance lines that won't match any expected Output directive
- Network operations can cause test timeouts or flakiness
Recommended solution: Rename to a non-Example function or convert to a simple example:
-func ExampleNew() { +func demonstrateUsage() { app := fiber.New() // ... rest of the codeAlternatively, if you want to keep it as a testable example, remove the server startup and simplify to a documentation-only example:
func ExampleNew() { // Create mock keytab for demonstration // In production, use: keytabLookup, _ := spnego.NewKeytabFileLookupFunc("/path/to/real.keytab") authMiddleware, err := spnego.New(spnego.Config{ KeytabLookup: keytabLookup, }) if err != nil { log.Fatal(err) } app := fiber.New() app.Use("/protected", authMiddleware) app.Get("/protected/resource", func(c fiber.Ctx) error { identity, ok := spnego.GetAuthenticatedIdentityFromContext(c) if !ok { return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized") } return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName())) }) fmt.Println("Middleware configured") // Output: Middleware configured }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
.github/workflows/test-spnego.ymlis excluded by!**/*.yml
📒 Files selected for processing (5)
v3/spnego/README.md(1 hunks)v3/spnego/example/example.go(1 hunks)v3/spnego/spnego.go(1 hunks)v3/spnego/spnego_test.go(1 hunks)v3/spnego/utils/mock_keytab.go(1 hunks)
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2024-11-23T19:50:41.765Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.
Applied to files:
v3/spnego/example/example.go
📚 Learning: 2024-11-23T19:35:36.767Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/main.go:0-0
Timestamp: 2024-11-23T19:35:36.767Z
Learning: In the Go `clean-code` example (`clean-code/app/main.go`) in the `gofiber/recipes` repository, it's acceptable to omit graceful shutdown handling, as the example code prioritizes simplicity over production-level practices.
Applied to files:
v3/spnego/example/example.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.
Applied to files:
v3/spnego/example/example.gov3/spnego/spnego_test.go
📚 Learning: 2024-11-23T19:51:05.812Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:18-20
Timestamp: 2024-11-23T19:51:05.812Z
Learning: In the `clean-code/app/server/server.go` file, the team prefers to maintain a simple health check endpoint without additional system status information.
Applied to files:
v3/spnego/example/example.go
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.
Applied to files:
v3/spnego/spnego.go
📚 Learning: 2025-10-16T07:15:26.529Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:15:26.529Z
Learning: In Fiber v3, net/http handlers (http.Handler, http.HandlerFunc, or raw func(http.ResponseWriter, *http.Request)) can be passed directly to routing methods like app.Get(), app.Post(), etc. The framework automatically detects and wraps them internally via toFiberHandler/collectHandlers. The github.com/gofiber/fiber/v3/middleware/adaptor package is legacy and should not be suggested for tests or code using native net/http handler support.
Applied to files:
v3/spnego/spnego.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.
Applied to files:
v3/spnego/spnego.go
📚 Learning: 2024-07-02T13:29:56.992Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-07-02T13:29:56.992Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.
Applied to files:
v3/spnego/spnego_test.go
📚 Learning: 2024-06-30T00:38:06.580Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-06-30T00:38:06.580Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.
Applied to files:
v3/spnego/spnego_test.go
📚 Learning: 2024-06-15T19:26:06.401Z
Learnt from: hcancelik
Repo: gofiber/fiber PR: 3036
File: docs/middleware/cache.md:103-103
Timestamp: 2024-06-15T19:26:06.401Z
Learning: There are no hard tabs in the lines 100 to 105 of the `docs/middleware/cache.md` file. Future comments about formatting should accurately reflect the actual content.
Applied to files:
v3/spnego/README.md
📚 Learning: 2025-11-13T11:34:38.503Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3824
File: listen_test.go:560-564
Timestamp: 2025-11-13T11:34:38.503Z
Learning: The gofiber/fiber project only supports the latest Go versions, so Go 1.22+ syntax features (such as `for range N` integer range loops) are acceptable and preferred.
Applied to files:
v3/spnego/README.md
🧬 Code graph analysis (4)
v3/spnego/example/example.go (5)
v3/spnego/spnego.go (1)
New(20-56)v3/spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-157)WithPrincipal(32-36)WithRealm(40-44)WithFilename(49-53)WithPairs(58-62)v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)v3/spnego/config.go (2)
NewKeytabFileLookupFunc(32-47)Config(23-28)v3/spnego/identity.go (1)
GetAuthenticatedIdentityFromContext(25-28)
v3/spnego/spnego.go (3)
v3/spnego/config.go (1)
Config(23-28)v3/spnego/error.go (3)
ErrConfigInvalidOfKeytabLookupFunctionRequired(6-6)ErrLookupKeytabFailed(9-9)ErrConvertRequestFailed(12-12)v3/spnego/identity.go (1)
SetAuthenticatedIdentityToContext(12-14)
v3/spnego/utils/mock_keytab.go (2)
v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)v3/spnego/spnego.go (1)
New(20-56)
v3/spnego/spnego_test.go (6)
v3/spnego/spnego.go (1)
New(20-56)v3/spnego/config.go (2)
Config(23-28)NewKeytabFileLookupFunc(32-47)v3/spnego/error.go (2)
ErrConfigInvalidOfKeytabLookupFunctionRequired(6-6)ErrLookupKeytabFailed(9-9)v3/spnego/utils/mock_keytab.go (5)
NewMockKeytab(123-157)WithPrincipal(32-36)WithRealm(40-44)WithFilename(49-53)WithPairs(58-62)v3/spnego/utils/keytab.go (1)
EncryptTypePair(22-26)v3/spnego/identity.go (1)
GetAuthenticatedIdentityFromContext(25-28)
🪛 markdownlint-cli2 (0.18.1)
v3/spnego/README.md
40-40: Hard tabs
Column: 1
(MD010, no-hard-tabs)
41-41: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
44-44: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
50-50: Hard tabs
Column: 1
(MD010, no-hard-tabs)
51-51: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
61-61: Hard tabs
Column: 1
(MD010, no-hard-tabs)
62-62: Hard tabs
Column: 1
(MD010, no-hard-tabs)
63-63: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
65-65: Hard tabs
Column: 1
(MD010, no-hard-tabs)
66-66: Hard tabs
Column: 1
(MD010, no-hard-tabs)
67-67: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
74-74: Hard tabs
Column: 1
(MD010, no-hard-tabs)
75-75: Hard tabs
Column: 1
(MD010, no-hard-tabs)
76-76: Hard tabs
Column: 1
(MD010, no-hard-tabs)
77-77: Hard tabs
Column: 1
(MD010, no-hard-tabs)
78-78: Hard tabs
Column: 1
(MD010, no-hard-tabs)
79-79: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
82-82: Hard tabs
Column: 1
(MD010, no-hard-tabs)
84-84: Hard tabs
Column: 1
(MD010, no-hard-tabs)
85-85: Hard tabs
Column: 1
(MD010, no-hard-tabs)
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
88-88: Hard tabs
Column: 1
(MD010, no-hard-tabs)
89-89: Hard tabs
Column: 1
(MD010, no-hard-tabs)
90-90: Hard tabs
Column: 1
(MD010, no-hard-tabs)
91-91: Hard tabs
Column: 1
(MD010, no-hard-tabs)
93-93: Hard tabs
Column: 1
(MD010, no-hard-tabs)
94-94: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🔇 Additional comments (2)
v3/spnego/spnego_test.go (1)
18-91: LGTM! Comprehensive test coverage.The test suite effectively covers:
- Config validation (missing KeytabLookup)
- Error handling (KeytabLookup failure)
- Successful middleware setup with multiple keytabs
- Unauthenticated request handling (401 response)
The use of mock keytabs and cleanup functions demonstrates good testing practices.
v3/spnego/utils/mock_keytab.go (1)
123-157: LGTM! Well-designed mock keytab utility.The implementation demonstrates several best practices:
- Clean functional options pattern for flexible configuration
- Proper error handling including file close operations (lines 143-146, 150-153)
- Testable design with the
fileOperatorinterface abstraction- Clear documentation and sensible defaults
- Resource cleanup via returned cleanup function
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.
Actionable comments posted: 0
🧹 Nitpick comments (1)
v3/spnego/spnego.go (1)
35-38: Defensively handle a nil keytab fromKeytabLookupIf a misconfigured
KeytabLookupreturns(*keytab.Keytab)(nil)with a nil error, this will flow intoSPNEGOKRB5Authenticateand may fail in a less obvious way. It’s cheap to guard against this and surface a clearer error.- kt, err := cfg.KeytabLookup() - if err != nil { - return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err) - } + kt, err := cfg.KeytabLookup() + if err != nil { + return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err) + } + if kt == nil { + return fmt.Errorf("%w: keytab lookup function returned nil", ErrLookupKeytabFailed) + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
v3/spnego/spnego.go(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.
Applied to files:
v3/spnego/spnego.go
📚 Learning: 2025-10-16T07:15:26.529Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:15:26.529Z
Learning: In Fiber v3, net/http handlers (http.Handler, http.HandlerFunc, or raw func(http.ResponseWriter, *http.Request)) can be passed directly to routing methods like app.Get(), app.Post(), etc. The framework automatically detects and wraps them internally via toFiberHandler/collectHandlers. The github.com/gofiber/fiber/v3/middleware/adaptor package is legacy and should not be suggested for tests or code using native net/http handler support.
Applied to files:
v3/spnego/spnego.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.
Applied to files:
v3/spnego/spnego.go
🧬 Code graph analysis (1)
v3/spnego/spnego.go (3)
v3/spnego/config.go (1)
Config(23-28)v3/spnego/error.go (2)
ErrConfigInvalidOfKeytabLookupFunctionRequired(6-6)ErrLookupKeytabFailed(9-9)v3/spnego/identity.go (1)
SetAuthenticatedIdentityToContext(12-14)
🔇 Additional comments (2)
v3/spnego/spnego.go (2)
20-31: Config validation and logger wiring look solid
Newcorrectly enforcescfg.KeytabLookupand buildsservice.Settingsoptions from eithercfg.Logor Fiber’s default logger, so SPNEGO logging gets a consistent*log.Loggerwithout per-request overhead. No changes needed here.
40-50: SPNEGO handler/adaptor flow looks correct; verify 401/header/identity behavior via testsThe per-request construction of
handler := adaptor.HTTPHandler( spnego.SPNEGOKRB5Authenticate(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { SetAuthenticatedIdentityToContext(ctx, goidentity.FromHTTPRequestContext(r)) handleErr = ctx.Next() }), kt, opts...), )gives
SPNEGOKRB5Authenticatefull control over issuing 401 +WWW-Authenticateand only advances Fiber’s chain on success.handleErrthen propagatesctx.Next()’s result, while unauthenticated flows return nil after SPNEGO has written the response, which matches Fiber middleware expectations. This also removes the previously broken custom adapter and relies on Fiber’s official adaptor implementation. Based on learningsTo be completely sure the external pieces behave as expected, please confirm via tests that:
- On missing/invalid
Authorization: Negotiatetokens, the response is 401 and includesWWW-Authenticate: Negotiate(and the next handler is not invoked).- On successful authentication, the next handler runs and
GetAuthenticatedIdentityFromContextsees the identity set bygoidentity.FromHTTPRequestContext.- Header mutations from the SPNEGO stack (especially
WWW-Authenticate) are visible on the Fiber response when usingadaptor.HTTPHandler.
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.
Pull Request Overview
Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if err = file.Close(); err != nil { | ||
| clean() | ||
| return nil, nil, fmt.Errorf("error closing file: %w", err) | ||
| } |
Copilot
AI
Nov 20, 2025
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.
The error handling logic here is confusing. After writing fails (line 142), the code checks if file.Close() fails (line 143), but if Close() succeeds, execution continues and still returns an error from Write() (line 148). The control flow should be simplified. Consider:
if _, err = kt.Write(file); err != nil {
_ = file.Close()
clean()
return nil, nil, fmt.Errorf("error writing to file: %w", err)
}| if err = file.Close(); err != nil { | |
| clean() | |
| return nil, nil, fmt.Errorf("error closing file: %w", err) | |
| } | |
| _ = file.Close() |
Co-authored-by: Copilot <[email protected]>
|
/gemini review |
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.
Code Review
This pull request introduces a new SPNEGO authentication middleware for Fiber. The implementation is comprehensive, including dynamic keytab loading, helper utilities for testing, and good documentation. The code is well-structured and makes good use of Go features like generics to support multiple Fiber versions.
My review has identified a few issues, including a critical bug in a utility function, a high-severity error handling bug, incorrect error wrapping, and some medium-severity issues with variable naming and dead code. After addressing these points, the middleware will be a solid addition to the Fiber contrib repository.
| for _, keytabFile := range keytabFiles { | ||
| kt, err := keytab.Load(keytabFile) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w: file %s load failed: %w", ErrLoadKeytabFileFailed, keytabFile, err) |
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.
An error format string can contain at most one %w verb. You are using it twice, which is invalid Go code and will be flagged by go vet. To wrap ErrLoadKeytabFileFailed while including context about the file and the underlying error, you should use %w once for the error you are wrapping and format the rest of the information as strings.
| return nil, fmt.Errorf("%w: file %s load failed: %w", ErrLoadKeytabFileFailed, keytabFile, err) | |
| return nil, fmt.Errorf("%w: file %s load failed: %v", ErrLoadKeytabFileFailed, keytabFile, err) |
v3/spnego/utils/keytab.go
Outdated
| for item := range maps.Values(keytabMap) { | ||
| mk = append(mk, item) | ||
| } |
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 a critical bug in this loop. for item := range maps.Values(keytabMap) iterates over the indices of the slice returned by maps.Values, not the values themselves. This means item is an integer. Trying to append this integer to mk (a slice of KeytabInfo) will result in a compilation error.
The correct approach is to iterate over the map directly.
| for item := range maps.Values(keytabMap) { | |
| mk = append(mk, item) | |
| } | |
| for _, item := range keytabMap { | |
| mk = append(mk, item) | |
| } |
| // Look up the keytab | ||
| kt, err := cfg.KeytabLookup() | ||
| if err != nil { | ||
| return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err) |
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.
The error from cfg.KeytabLookup() is being wrapped with %v, which converts the error to its string representation and breaks the error chain. To allow for proper error inspection with errors.Is and errors.As, you should use the %w format verb to wrap the error.
| return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err) | |
| return fmt.Errorf("%w: %w", ErrLookupKeytabFailed, err) |
| if _, err = kt.Write(file); err != nil { | ||
| if err = file.Close(); err != nil { | ||
| clean() | ||
| return nil, nil, fmt.Errorf("error closing file: %w", err) | ||
| } | ||
| clean() | ||
| return nil, nil, fmt.Errorf("error writing to file: %w", err) | ||
| } | ||
| if err = file.Close(); err != nil { | ||
| clean() | ||
| return nil, nil, fmt.Errorf("error closing file: %w", err) | ||
| } |
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 a bug in the error handling logic here. If kt.Write(file) fails, its error is stored in err. However, the subsequent call to file.Close() also assigns its result to err, overwriting the original (and more important) write error. If file.Close() succeeds, the write error is lost. If file.Close() fails, a misleading error is returned.
The error from kt.Write should be preserved. It's better to attempt to close the file but prioritize returning the write error.
if _, err = kt.Write(file); err != nil {
_ = file.Close() // Attempt to close, but prioritize the write error.
clean()
return nil, nil, fmt.Errorf("error writing to file: %w", err)
}
if err = file.Close(); err != nil {
clean()
return nil, nil, fmt.Errorf("error closing file: %w", err)
}Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Description
This PR adds enterprise-grade SPNEGO authentication middleware to Fiber, providing Kerberos-based single sign-on (SSO) capabilities. The implementation follows Fiber's middleware patterns with dynamic keytab loading and full RFC compliance.
Related Issue
Resolves #1367
Issue: SPNEGO Middleware Proposal
Technical Implementation
WWW-Authenticatechallenges andAuthorizationvalidationVerification