-
-
Notifications
You must be signed in to change notification settings - Fork 6
#184 Change CSRF implementation to default to using SessionStorage instead of CacheStorage to remove 'page expired' errors and make extensible. #238
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
99a5040
Change CSRF implementation to default to using SessionStorage instead…
grantcopley 7f1c63f
Initial plan
Copilot ac0d94a
Update models/services/csrf/CacheCSRFStorage.cfc
grantcopley 4608c4b
Add test coverage for csrfEnabled = false scenarios
Copilot 0f95972
Update models/services/csrf/SessionCSRFStorage.cfc
grantcopley 3a377df
Initial plan
Copilot 04e2170
Use cross-platform session detection with getApplicationMetadata()
Copilot f1b69f9
Cache app metadata and improve catch block comment
Copilot a62978b
Initialize appMetadata as struct and use structCount check
Copilot d297b39
Use structIsEmpty for better readability
Copilot b9d0d44
Merge pull request #250 from coldbox-modules/copilot/sub-pr-238
grantcopley 35b3f5d
Merge pull request #251 from coldbox-modules/copilot/sub-pr-238-again
grantcopley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(find:*)", | ||
| "Bash(box testbox run:*)", | ||
| "WebSearch", | ||
| "WebFetch(domain:github.com)" | ||
| ], | ||
| "deny": [], | ||
| "ask": [] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /** | ||
| * Interface for CSRF token storage implementations in cbwire. | ||
| * | ||
| * Implementations must provide thread-safe storage and retrieval | ||
| * of CSRF tokens with session-lifetime persistence. | ||
| * | ||
| * @author Ortus Solutions | ||
| */ | ||
| interface { | ||
|
|
||
| /** | ||
| * Stores a CSRF token for the current session/user context. | ||
| * | ||
| * @token The CSRF token to store | ||
| * | ||
| * @return The storage implementation instance (for method chaining) | ||
| */ | ||
| any function set( required string token ); | ||
|
|
||
| /** | ||
| * Retrieves the stored CSRF token for the current session/user context. | ||
| * | ||
| * @return The stored token, or empty string if no token exists | ||
| */ | ||
| string function get(); | ||
|
|
||
| /** | ||
| * Checks if a CSRF token exists in storage for the current session/user context. | ||
| * | ||
| * @return True if a token exists, false otherwise | ||
| */ | ||
| boolean function exists(); | ||
|
|
||
| /** | ||
| * Removes the CSRF token from storage (used for rotation/logout). | ||
| * | ||
| * @return The storage implementation instance (for method chaining) | ||
| */ | ||
| any function delete(); | ||
|
|
||
| /** | ||
| * Clears all CSRF-related data from storage. | ||
| * Useful for cleanup or testing scenarios. | ||
| * | ||
| * @return The storage implementation instance (for method chaining) | ||
| */ | ||
| any function clear(); | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| /** | ||
| * CBWIRE-specific token service for preventing CSRF attacks | ||
| * without interfering with cbcsrf module settings. | ||
| * | ||
| * Delegates storage operations to a configurable ICSRFStorage implementation. | ||
| * | ||
| * @author Ortus Solutions | ||
| */ | ||
| component accessors="true" singleton { | ||
|
|
||
| property name="moduleSettings" inject="coldbox:modulesettings:cbwire"; | ||
| property name="wirebox" inject="wirebox"; | ||
|
|
||
| /** | ||
| * Lazy-loaded CSRF storage service | ||
| */ | ||
| variables.csrfService = ""; | ||
|
|
||
| /** | ||
| * Cache for application metadata to avoid repeated lookups | ||
| */ | ||
| variables.appMetadata = {}; | ||
|
|
||
| /** | ||
| * Generates a CBWIRE-specific token that doesn't expire. | ||
| * Stored using the configured storage implementation and lasts for session lifetime. | ||
| * | ||
| * @return The generated or existing token | ||
| */ | ||
| function generate() { | ||
| // If we don't have a token yet, generate one | ||
| if ( !getCSRFService().exists() ) { | ||
| var newToken = generateNewToken(); | ||
| getCSRFService().set( newToken ); | ||
| } | ||
|
|
||
| return getCSRFService().get(); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies a CBWIRE token | ||
| * | ||
| * @token The token to verify | ||
| * | ||
| * @return True if valid, false otherwise | ||
| */ | ||
| function verify( required string token ) { | ||
| // Verify token exists and matches | ||
| return getCSRFService().exists() && getCSRFService().get() == arguments.token; | ||
grantcopley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Rotates the token (for logout, etc) | ||
| * | ||
| * @return TokenService instance for chaining | ||
| */ | ||
| function rotate() { | ||
| getCSRFService().delete(); | ||
| return this; | ||
| } | ||
|
|
||
| // Private methods | ||
|
|
||
| /** | ||
| * Lazy-loads the CSRF storage service based on module settings | ||
| * | ||
| * @return The configured ICSRFStorage implementation | ||
| */ | ||
| private function getCSRFService() { | ||
| if ( !isObject( variables.csrfService ) ) { | ||
| variables.csrfService = wirebox.getInstance( moduleSettings.csrfService ); | ||
| } | ||
| return variables.csrfService; | ||
| } | ||
|
|
||
| private function generateNewToken() { | ||
| // Generate a cryptographically secure random token | ||
| var tokenBase = "#createUUID()##getRealIP()##randRange( 0, 65535, "SHA1PRNG" )##getTickCount()#"; | ||
|
|
||
| // Include session ID if sessions are enabled (cross-platform check) | ||
| var sessionId = ""; | ||
| if ( isSessionManagementEnabled() ) { | ||
| try { | ||
| sessionId = session.sessionid; | ||
| } catch ( any e ) { | ||
| // Handle cases where session scope exists but sessionid property is not yet available, | ||
| // or when session operations fail during application startup | ||
| } | ||
| } | ||
|
|
||
| return uCase( left( hash( tokenBase & sessionId, "SHA-256" ), 40 ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Checks if session management is enabled in the application (cross-platform) | ||
| * | ||
| * @return True if session management is enabled, false otherwise | ||
| */ | ||
| private function isSessionManagementEnabled() { | ||
| if ( structIsEmpty( variables.appMetadata ) ) { | ||
| variables.appMetadata = getApplicationMetadata(); | ||
| } | ||
| return structKeyExists( variables.appMetadata, "sessionManagement" ) && variables.appMetadata.sessionManagement; | ||
| } | ||
|
|
||
| private function getRealIP() { | ||
| var headers = getHTTPRequestData().headers; | ||
|
|
||
| if ( structKeyExists( headers, "x-cluster-client-ip" ) ) { | ||
| return headers[ "x-cluster-client-ip" ]; | ||
| } | ||
| if ( structKeyExists( headers, "X-Forwarded-For" ) ) { | ||
| return headers[ "X-Forwarded-For" ]; | ||
| } | ||
|
|
||
| return len( CGI.REMOTE_ADDR ) ? CGI.REMOTE_ADDR : "127.0.0.1"; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| /** | ||
| * Cache-based CSRF token storage using cbstorages cacheStorage. | ||
| * Suitable for distributed/clustered environments where session replication | ||
| * is not available or desired. | ||
| * | ||
| * Tokens are stored in the configured CacheBox cache provider. | ||
| * Does not require session management to be enabled - uses cookie/URL fallback. | ||
| * | ||
| * @author Ortus Solutions | ||
| */ | ||
| component | ||
| accessors="true" | ||
| singleton | ||
| implements="cbwire.models.interfaces.ICSRFStorage" | ||
| { | ||
|
|
||
| property name="cacheStorage" inject="cacheStorage@cbstorages"; | ||
|
|
||
| /** | ||
| * The storage key used for cbwire tokens | ||
| */ | ||
| variables.STORAGE_KEY = "_cbwire_token"; | ||
|
|
||
| /** | ||
| * Stores a CSRF token in cache storage | ||
| * | ||
| * @token The CSRF token to store | ||
| */ | ||
| function set( required string token ) { | ||
| cacheStorage.set( | ||
| variables.STORAGE_KEY, | ||
| { "token": arguments.token, "createdAt": now() } | ||
| ); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the stored CSRF token from cache | ||
| * | ||
| * @return The stored token, or empty string if none exists | ||
| */ | ||
| string function get() { | ||
| var data = cacheStorage.get( variables.STORAGE_KEY, {} ); | ||
| return data.keyExists( "token" ) ? data.token : ""; | ||
| } | ||
|
|
||
| /** | ||
| * Checks if a token exists in cache storage | ||
| */ | ||
| boolean function exists() { | ||
| var data = cacheStorage.get( variables.STORAGE_KEY, {} ); | ||
| return data.keyExists( "token" ) && len( data.token ) > 0; | ||
| } | ||
|
|
||
| /** | ||
| * Removes the token from cache storage | ||
| */ | ||
| function delete() { | ||
| cacheStorage.delete( variables.STORAGE_KEY ); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Clears all CSRF data (same as delete for cache storage) | ||
| */ | ||
| function clear() { | ||
| return delete(); | ||
| } | ||
|
|
||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.