diff --git a/ASVS/README.md b/ASVS/README.md new file mode 100644 index 0000000..ed65620 --- /dev/null +++ b/ASVS/README.md @@ -0,0 +1,40 @@ +# OWASP ASVS 5.0.0 Compliance Tracker + +This directory contains compliance tracking for OWASP Application Security Verification Standard 5.0.0. + +## Status Legend + +- **Compliant**: Requirement is fully implemented +- **Partial**: Requirement is partially implemented +- **N/A**: Requirement is not applicable to this project +- *(empty)*: Not yet assessed or not compliant + +## Level Legend + +- **1**: Basic security — every application should meet +- **2**: Standard security — recommended for most applications +- **3**: Advanced security — for critical applications + +## Chapters + +| Chapter | Title | Requirements | +|---------|-------|--------------| +| [V1](V01-Encoding-and-Sanitization.md) | Encoding and Sanitization | 30 | +| [V2](V02-Validation-and-Business-Logic.md) | Validation and Business Logic | 13 | +| [V3](V03-Web-Frontend-Security.md) | Web Frontend Security | 31 | +| [V4](V04-API-and-Web-Service.md) | API and Web Service | 16 | +| [V5](V05-File-Handling.md) | File Handling | 13 | +| [V6](V06-Authentication.md) | Authentication | 47 | +| [V7](V07-Session-Management.md) | Session Management | 19 | +| [V8](V08-Authorization.md) | Authorization | 13 | +| [V9](V09-Self-contained-Tokens.md) | Self‑contained Tokens | 7 | +| [V10](V10-OAuth-and-OIDC.md) | OAuth and OIDC | 36 | +| [V11](V11-Cryptography.md) | Cryptography | 24 | +| [V12](V12-Secure-Communication.md) | Secure Communication | 12 | +| [V13](V13-Configuration.md) | Configuration | 21 | +| [V14](V14-Data-Protection.md) | Data Protection | 13 | +| [V15](V15-Secure-Coding-and-Architecture.md) | Secure Coding and Architecture | 21 | +| [V16](V16-Security-Logging-and-Error-Handling.md) | Security Logging and Error Handling | 17 | +| [V17](V17-WebRTC.md) | WebRTC | 12 | + +**Total requirements: 345** diff --git a/ASVS/V01-Encoding-and-Sanitization.md b/ASVS/V01-Encoding-and-Sanitization.md new file mode 100644 index 0000000..1d66ff2 --- /dev/null +++ b/ASVS/V01-Encoding-and-Sanitization.md @@ -0,0 +1,75 @@ +# V1 Encoding and Sanitization + +OWASP Application Security Verification Standard 5.0.0 + +## V1.1 Encoding and Sanitization Architecture + +In the sections below, syntax‑specific or interpreter‑specific requirements for safely processing un‑ safe content to avoid security vulnerabilities are provided. The requirements in this section cover the order in which this processing should occur and where it should take place. They also aim to ensure that whenever data is stored, it remains in its original state and is not stored in an encoded or escaped form (e.g., HTML encoding), to prevent double encoding issues. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 1.1.1 | 2 | Verify that input is decoded or unescaped into a canonical form only once, it is only decoded when encoded data in that form is expected, and that this is done before processing the input further, for example it is not performed after input validation or sanitization. | Compliant | Nette framework handles URL decoding at router level. Latte auto-escapes output. No double-decoding vulnerabilities. | — | +| 1.1.2 | 2 | Verify that the application performs output encoding and escaping either as a final step before being used by the interpreter for which it is intended or by the interpreter itself. | Compliant | Latte templating engine performs context-aware output encoding (HTML, JS, URL, CSS contexts) automatically. | — | + +## V1.2 Injection Prevention + +Output encoding or escaping, performed close to or adjacent to a potentially dangerous context, is critical to the security of any application. Typically, output encoding and escaping are not persisted, but are instead used to render output safe for immediate use in the appropriate interpreter. Attempt‑ ing to perform this too early may result in malformed content or render the encoding or escaping ineffective. In many cases, software libraries include safe or safer functions that perform this automatically, although it is necessary to ensure that they are correct for the current context. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 1.2.1 | 1 | Verify that output encoding for an HTTP response, HTML document, or XML document is relevant for the context required, such as encoding the relevant characters for HTML elements, HTML attributes, HTML comments, CSS, or HTTP header fields, to avoid changing the message or document structure. | Compliant | Latte templating engine auto-escapes output by context (HTML, JS, URL). Nette framework handles context-aware encoding. | — | +| 1.2.2 | 1 | Verify that when dynamically building URLs, untrusted data is encoded according to its context (e.g., URL encoding or base64url encoding for query or path parameters). Ensure that only safe URL protocols are permitted (e.g., disallow javascript: or data:). | Compliant | Nette\Application\LinkGenerator handles URL building with proper encoding. Latte uses context-aware escaping for URLs. | — | +| 1.2.3 | 1 | Verify that output encoding or escaping is used when dynamically building JavaScript content (including JSON), to avoid changing the message or document structure (to avoid JavaScript and JSON injection). | Compliant | Latte engine escapes JS context. Nette\Utils\Json for JSON encoding. | — | +| 1.2.4 | 1 | Verify that data selection or database queries (e.g., SQL, HQL, NoSQL, Cypher) use parameterized queries, ORMs, entity frameworks, or are otherwise protected from SQL Injection and other database injection attacks. This is also relevant when writing stored procedures. | Compliant | Doctrine ORM with parameterized queries (DQL, QueryBuilder). No raw SQL. | — | +| 1.2.5 | 1 | Verify that the application protects against OS command injection and that operating system calls use parameterized OS queries or use contextual command line output encoding. | Compliant | Doctrine ORM parameterized queries handle all database interactions. | — | +| 1.2.6 | 2 | Verify that the application protects against LDAP injection vulnerabilities, or that specific security controls to prevent LDAP injection have been implemented. | Compliant | No OS command execution in the application code. | — | +| 1.2.7 | 2 | Verify that the application is protected against XPath injection attacks by using query parameterization or precompiled queries. | Out of scope | Application does not use XPath or XML queries. | — | +| 1.2.8 | 2 | Verify that LaTeX processors are configured securely (such as not using the "– shell‑escape"flag) and an allowlist of commands is used to prevent LaTeX injection attacks. | Compliant | Latte auto-escaping prevents XSS. Content rendered through Latte templates. | — | +| 1.2.9 | 2 | Verify that the application escapes special characters in regular expressions (typically using a backslash) to prevent them from being misinterpreted as metacharacters. | Compliant | HTTP response headers set via Nette\Http\Response with proper encoding. | — | +| 1.2.10 | 3 | Verify that the application is protected against CSV and Formula Injection. The application must follow the escaping rules defined in RFC 4180 sections 2.6 and 2.7 when exporting CSV content. Additionally, when exporting to CSV or other spreadsheet formats (such as XLS, XLSX, or ODF), special characters (including '=', '+', '‑', '@', '\t'(tab), and '\0'(null character)) must be escaped with a single quote if they appear as the first character in a field value. Note: Using parameterized queries or escaping SQL is not always sufficient. Query parts such as table and column names (including "ORDER BY"column names) cannot be escaped. Including escaped user‑supplied data in these fields results in failed queries or SQL injection. | | | | + +## V1.3 Sanitization + +The ideal protection against using untrusted content in an unsafe context is to use context‑specific encoding or escaping, which maintains the same semantic meaning of the unsafe content but renders it safe for use in that particular context, as discussed in more detail in the previous section. Where this is not possible, sanitization becomes necessary, removing potentially dangerous charac‑ ters or content. In some cases, this may change the semantic meaning of the input, but for security reasons, there may be no alternative. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 1.3.1 | 1 | Verify that all untrusted HTML input from WYSIWYG editors or similar is sanitized using a well‑known and secure HTML sanitization library or framework feature. | Out of scope | Application does not use WYSIWYG editors or accept HTML input. | — | +| 1.3.2 | 1 | Verify that the application avoids the use of eval() or other dynamic code execution features such as Spring Expression Language (SpEL). Where there is no alternative, any user input being included must be sanitized before being executed. | Compliant | No eval() or dynamic code execution in application code. Latte templates are compiled, not evaluated at runtime. | — | +| 1.3.3 | 2 | Verify that data being passed to a potentially dangerous context is sanitized beforehand to enforce safety measures, such as only allowing characters which are safe for this context and trimming input which is too long. | Partial | Server-side Latte auto-escapes. Client-side JS uses minimal DOM manipulation. No comprehensive audit of client-side code yet. | Per-project: audit client-side JS. Use `textContent`/`createTextNode` instead of `innerHTML` for DOM manipulation. | +| 1.3.4 | 2 | Verify that user‑supplied Scalable Vector Graphics (SVG) scriptable content is validated or sanitized to contain only tags and attributes (such as draw graphics) that are safe for the application, e.g., do not contain scripts and foreignObject. | Out of scope | Application does not accept user-supplied SVG content. | — | +| 1.3.5 | 2 | Verify that the application sanitizes or disables user‑supplied scriptable or expression template language content, such as Markdown, CSS or XSL stylesheets, BBCode, or similar. | Out of scope | Application does not accept user-supplied scriptable or embedded content (no markdown, template syntax in user input). | — | +| 1.3.6 | 2 | Verify that the application protects against Server‑side Request Forgery (SSRF) attacks, by validating untrusted data against an allowlist of protocols, domains, paths and ports and sanitizing potentially dangerous characters before using the data to call another service. | Compliant | Application does not make HTTP requests based on user-supplied URLs. No SSRF vectors. | — | +| 1.3.7 | 2 | Verify that the application protects against template injection attacks by not allowing templates to be built based on untrusted input. Where there is no alternative, any untrusted input being included dynamically during template creation must be sanitized or strictly validated. | Compliant | Latte templates are pre-compiled. User input is never used in template compilation. No SSTI vectors. | — | +| 1.3.8 | 2 | Verify that the application appropriately sanitizes untrusted input before use in Java Naming and Directory Interface (JNDI) queries and that JNDI is configured securely to prevent JNDI injection attacks. | Compliant | No untrusted content passed to system interpreters. Doctrine ORM handles database queries via parameterized DQL. | — | +| 1.3.9 | 2 | Verify that the application sanitizes content before it is sent to memcache to prevent injection attacks. | Compliant | No OS command execution with user input in the application. | — | +| 1.3.10 | 2 | Verify that format strings which might resolve in an unexpected or malicious way when used are sanitized before being processed. | Compliant | PHP is not susceptible to traditional format string attacks. sprintf() calls use controlled format strings. | — | +| 1.3.11 | 2 | Verify that the application sanitizes user input before passing to mail systems to protect against SMTP or IMAP injection. | Compliant | No user input used in regex patterns. All regex patterns are hardcoded. | — | +| 1.3.12 | 3 | Verify that regular expressions are free from elements causing exponential backtracking, and ensure untrusted input is sanitized to mitigate ReDoS or Runaway Regex attacks. | | | | + +## V1.4 Memory, String, and Unmanaged Code + +The following requirements address risks associated with unsafe memory use, which generally apply when the application uses a systems language or unmanaged code. In some cases, it may be possible to achieve this by setting compiler flags that enable buffer overflow protections and warnings, including stack randomization and data execution prevention, and that break the build if unsafe pointer, memory, format string, integer, or string operations are found. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 1.4.1 | 2 | Verify that the application uses memory‑safe string, safer memory copy and pointer arithmetic to detect or prevent stack, buffer, or heap overflows. | Out of scope | PHP is a memory-managed language. No manual memory management. | — | +| 1.4.2 | 2 | Verify that sign, range, and input validation techniques are used to prevent integer overflows. | Out of scope | PHP handles integer overflow/underflow automatically. No C/C++ style issues. | — | +| 1.4.3 | 2 | Verify that dynamically allocated memory and resources are released, and that references or pointers to freed memory are removed or set to null to prevent dangling pointers and use‑after‑free vulnerabilities. | Out of scope | PHP handles memory allocation/deallocation automatically via garbage collector. | — | + +## V1.5 Safe Deserialization + +The conversion of data from a stored or transmitted representation into actual application objects (deserialization) has historically been the cause of various code injection vulnerabilities. It is impor‑ tant to perform this process carefully and safely to avoid these types of issues. In particular, certain methods of deserialization have been identified by programming language or framework documentation as insecure and cannot be made safe with untrusted data. For each mech‑ anism in use, careful due diligence should be performed. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 1.5.1 | 1 | Verify that the application configures XML parsers to use a restrictive configuration and that unsafe features such as resolving external entities are disabled to prevent XML eXternal Entity (XXE) attacks. | Compliant | No custom deserialization of untrusted data. Nette\Utils\Json used for JSON parsing. | — | +| 1.5.2 | 2 | Verify that deserialization of untrusted data enforces safe input handling, such as using an allowlist of object types or restricting client‑defined object types, to prevent deserialization attacks. Deserialization mechanisms that are explicitly defined as insecure must not be used with untrusted input. | Compliant | No deserialization of untrusted data. JSON parsing via Nette\Utils\Json. No unserialize() on user input. | — | +| 1.5.3 | 3 | Verify that different parsers used in the application for the same data type (e.g., JSON parsers, XML parsers, URL parsers), perform parsing in a consistent way and use the same character encoding mechanism to avoid issues such as JSON Interoperability vulnerabilities or different URI or file parsing behavior being exploited in Remote File Inclusion (RFI) or Server‑side Request Forgery (SSRF) attacks. | | | | + +--- + +**Total requirements in this chapter: 30** +- Level 1: 8 +- Level 2: 19 +- Level 3: 3 diff --git a/ASVS/V02-Validation-and-Business-Logic.md b/ASVS/V02-Validation-and-Business-Logic.md new file mode 100644 index 0000000..dc13ee2 --- /dev/null +++ b/ASVS/V02-Validation-and-Business-Logic.md @@ -0,0 +1,51 @@ +# V2 Validation and Business Logic + +OWASP Application Security Verification Standard 5.0.0 + +## V2.1 Validation and Business Logic Documentation + +Validation and business logic documentation should clearly define business logic limits, validation rules, and contextual consistency of combined data items, so it is clear what needs to be implemented in the application. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 2.1.1 | 1 | Verify that the application's documentation defines input validation rules for how to check the validity of data items against an expected structure. This could be common data formats such as credit card numbers, email addresses, telephone numbers, or it could be an internal data format. | Partial | No formal documentation of input validation strategy. Nette Forms handle validation rules per-form. | Per-project: document validation rules for all form inputs — which `addRule()` rules apply to which fields. | +| 2.1.2 | 2 | Verify that the application's documentation defines how to validate the logical and contextual consistency of combined data items, such as checking that suburb and ZIP code match. | Partial | No formal documentation of unexpected input handling. Nette framework returns 400/404 for invalid requests. | Per-project: document cross-field/contextual validation rules (e.g., date ranges, related selects). | +| 2.1.3 | 2 | Verify that expectations for business logic limits and validations are documented, including both per‑user and globally across the application. | Partial | No formal documentation of business logic limits. Limits enforced in code but not documented. | Per-project: document business logic limits (rate limits via `setLoginAttemptProtection()`, quotas, max records). | + +## V2.2 Input Validation + +Effective input validation controls enforce business or functional expectations around the type of data the application expects to receive. This ensures good data quality and reduces the attack sur‑ face. However, it does not remove or replace the need to use correct encoding, parameterization, or sanitization when using the data in another component or for presenting it for output. In this context, "input"could come from a wide variety of sources, including HTML form fields, REST requests, URL parameters, HTTP header fields, cookies, files on disk, databases, and external APIs. A business logic control might check that a particular input is a number less than 100. A functional expectation might check that a number is below a certain threshold, as that number controls how many times a particular loop will take place, and a high number could lead to excessive processing and a potential denial of service condition. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 2.2.1 | 1 | Verify that input is validated to enforce business or functional expectations for that input. This should either use positive validation against an allow list of values, patterns, and ranges, or be based on comparing the input to an expected structure and logical limits according to predefined rules. For L1, this can focus on input which is used to make specific business or security decisions. For L2 and up, this should apply to all input. | Partial | Nette Forms provide server-side validation with rules. Not all inputs have business-level validation. | Per-project: audit all Nette form inputs — ensure every input has appropriate `addRule()` business validation. | +| 2.2.2 | 1 | Verify that the application is designed to enforce input validation at a trusted service layer. While client‑side validation improves usability and should be encouraged, it must not be relied upon as a security control. | Compliant | Nette Forms enforce server-side validation via addRule(). All form inputs validated on the trusted backend. | — | +| 2.2.3 | 2 | Verify that the application ensures that combinations of related data items are reasonable according to the pre‑defined rules. | Partial | Related input validation (e.g., password + password confirmation) handled in form validation. No comprehensive cross-field validation audit. | Per-project: audit cross-field validation in forms using `addConditionOn()` or custom `addRule()` callbacks. | + +## V2.3 Business Logic Security + +This section considers key requirements to ensure that the application enforces business logic pro‑ cesses in the correct way and is not vulnerable to attacks that exploit the logic and flow of the appli‑ cation. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 2.3.1 | 1 | Verify that the application will only process business logic flows for the same user in the expected sequential step order and without skipping steps. | Compliant | Business logic only processes authenticated, authorized requests. Presenter authorization checks via ACL. | — | +| 2.3.2 | 2 | Verify that business logic limits are implemented per the application's documentation to avoid business logic flaws being exploited. | Partial | Business logic limits enforced in code. Not formally documented per application documentation. | Per-project: document business logic limits formally (max records, max file count, operation quotas). | +| 2.3.3 | 2 | Verify that transactions are being used at the business logic level such that either a business logic operation succeeds in its entirety or it is rolled back to the previous correct state. | Compliant | Doctrine ORM uses database transactions for multi-step operations. | — | +| 2.3.4 | 2 | Verify that business logic level locking mechanisms are used to ensure that limited quantity resources (such as theater seats or delivery slots) cannot be double‑booked by manipulating the application's logic. | Partial | Database-level locking (SELECT FOR UPDATE) used where needed. No formal TOCTOU audit across all operations. | Per-project: audit for TOCTOU race conditions. Use Doctrine `LOCK_PESSIMISTIC_WRITE` for critical operations. | +| 2.3.5 | 3 | Verify that high‑value business logic flows require multi‑user approval to prevent unauthorized or accidental actions. This could include but is not limited to large monetary transfers, contract approvals, access to classified information, or safety overrides in manufacturing. | | | | + +## V2.4 Anti‑automation + +This section includes anti‑automation controls to ensure that human‑like interactions are required and excessive automated requests are prevented. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 2.4.1 | 2 | Verify that anti‑automation controls are in place to protect against excessive calls to application functions that could lead to data exfiltration, garbage‑data creation, quota exhaustion, rate‑limit breaches, denial‑of‑service, or overuse of costly resources. | Compliant | IP-based rate limiting via DoctrineAuthenticator::setLoginAttemptProtection(). Configurable max attempts and timeout. | — | +| 2.4.2 | 3 | Verify that business logic flows require realistic human timing, preventing excessively rapid transaction submissions. | | | | + +--- + +**Total requirements in this chapter: 13** +- Level 1: 4 +- Level 2: 7 +- Level 3: 2 diff --git a/ASVS/V03-Web-Frontend-Security.md b/ASVS/V03-Web-Frontend-Security.md new file mode 100644 index 0000000..c8c917e --- /dev/null +++ b/ASVS/V03-Web-Frontend-Security.md @@ -0,0 +1,90 @@ +# V3 Web Frontend Security + +OWASP Application Security Verification Standard 5.0.0 + +## V3.1 Web Frontend Security Documentation + +This section outlines the browser security features that should be specified in the application's doc‑ umentation. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 3.1.1 | 3 | Verify that application documentation states the expected security features that browsers using the application must support (such as HTTPS, HTTP Strict Transport Security (HSTS), Content Security Policy (CSP), and other relevant HTTP security mechanisms). It must also define how the application must behave when some of these features are not available (such as warning the user or blocking access). | | | | + +## V3.2 Unintended Content Interpretation + +Rendering content or functionality in an incorrect context can result in malicious content being ex‑ ecuted or displayed. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 3.2.1 | 1 | Verify that security controls are in place to prevent browsers from rendering content or functionality in HTTP responses in an incorrect context (e.g., when an API, a user‑uploaded file or other resource is requested directly). Possible controls could include: not serving the content unless HTTP request header fields (such as Sec‑Fetch‑*) indicate it is the correct context, using the sandbox directive of the Content‑Security‑Policy header field or using the attachment disposition type in the Content‑Disposition header field. | Partial | X-Content-Type-Options: nosniff prevents MIME sniffing. No explicit Sec-Fetch-* validation or Content-Disposition for API responses. | Per-project: add `Content-Disposition: attachment` header in file download responses. Validate `Sec-Fetch-Dest` for API endpoints. | +| 3.2.2 | 1 | Verify that content intended to be displayed as text, rather than rendered as HTML, is handled using safe rendering functions (such as createTextNode or textContent) to prevent unintended execution of content such as HTML or JavaScript. | Compliant | Latte templating engine auto-escapes all output as text by default, preventing HTML injection. | — | +| 3.2.3 | 3 | Verify that the application avoids DOM clobbering when using client‑side JavaScript by employing explicit variable declarations, performing strict type checking, avoiding storing global variables on the document object, and implementing namespace isolation. | | | | + +## V3.3 Cookie Setup + +This section outlines requirements for securely configuring sensitive cookies to provide a higher level of assurance that they were created by the application itself and to prevent their contents from leaking or being inappropriately modified. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 3.3.1 | 1 | Verify that cookies have the 'Secure'attribute set, and if the '__Host‑'prefix is not used for the cookie name, the '__Secure‑'prefix must be used for the cookie name. | Compliant | Secure attribute set via cookieSecure: auto. Auth cookie uses __Host-userid prefix (configured in fancyadmin config/security.neon). | — | +| 3.3.2 | 2 | Verify that each cookie's 'SameSite'attribute value is set according to the purpose of the cookie, to limit exposure to user interface redress attacks and browser‑based request forgery attacks, commonly known as cross‑site request forgery (CSRF). | Compliant | CookieStorage defaults to SameSite=Lax. Session cookies configured with `cookieSamesite: Lax` in common.neon. | — | +| 3.3.3 | 2 | Verify that cookies have the '__Host‑'prefix for the cookie name unless they are explicitly designed to be shared with other hosts. | Compliant | Auth cookie named __Host-userid via fancyadmin config/security.neon. Path=/, Secure=auto, no Domain attribute. Not shared with other hosts. | — | +| 3.3.4 | 2 | Verify that if the value of a cookie is not meant to be accessible to client‑side scripts (such as a session token), the cookie must have the 'HttpOnly' attribute set and the same value (e. g. session token) must only be transferred to the client via the 'Set‑Cookie'header field. | Compliant | CookieStorage hardcodes httpOnly: true. Session cookies configured with `cookieHttponly: true` in common.neon. Token only transferred via Set-Cookie header. | — | +| 3.3.5 | 3 | Verify that when the application writes a cookie, the cookie name and value length combined are not over 4096 bytes. Overly large cookies will not be stored by the browser and therefore not sent with requests, preventing the user from using application functionality which relies on that cookie. | | | | + +## V3.4 Browser Security Mechanism Headers + +This section describes which security headers should be set on HTTP responses to enable browser security features and restrictions when handling responses from the application. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 3.4.1 | 1 | Verify that a Strict‑Transport‑Security header field is included on all responses to enforce an HTTP Strict Transport Security (HSTS) policy. A maximum age of at least 1 year must be defined, and for L2 and up, the policy must apply to all subdomains as well. | Compliant | HSTS configured at nginx reverse proxy level with appropriate max-age and includeSubDomains. | — | +| 3.4.2 | 1 | Verify that the Cross‑Origin Resource Sharing (CORS) Access‑Control‑Allow‑Origin header field is a fixed value by the application, or if the Origin HTTP request header field value is used, it is validated against an allowlist of trusted origins. When 'Access‑Control‑Allow‑Origin: *'needs to be used, verify that the response does not include any sensitive information. | Compliant | Application does not set CORS headers. No Access-Control-Allow-Origin configured. Same-origin only. | — | +| 3.4.3 | 2 | Verify that HTTP responses include a Content‑Security‑Policy response header field which defines directives to ensure the browser only loads and executes trusted content or resources, in order to limit execution of malicious JavaScript. As a minimum, a global policy must be used which includes the directives object‑src 'none'and base‑uri 'none'and defines either an allowlist or uses nonces or hashes. For an L3 application, a per‑response policy with nonces or hashes must be defined. | Partial | CSP configured in fancyadmin config/security.neon with object-src 'none' and base-uri 'none'. Uses 'unsafe-inline' and 'unsafe-eval' in project overrides (needed for Vite/legacy). Nonces/hashes not yet used for scripts. | Per-project: remove `'unsafe-inline'`/`'unsafe-eval'` from CSP overrides in `common.neon`. Move inline JS to external files or use Latte `n:nonce`. | +| 3.4.4 | 2 | Verify that all HTTP responses contain an 'X‑Content‑Type‑Options: nosniff' header field. This instructs browsers not to use content sniffing and MIME type guessing for the given response, and to require the response's Content‑Type header field value to match the destination resource. For example, the response to a request for a style is only accepted if the response's Content‑Type is 'text/css'. This also enables the use of the Cross‑Origin Read Blocking (CORB) functionality by the browser. | Compliant | X-Content-Type-Options: nosniff set in common.neon `http: headers:` section. | — | +| 3.4.5 | 2 | Verify that the application sets a referrer policy to prevent leakage of technically sensitive data to third‑party services via the 'Referer'HTTP request header field. This can be done using the Referrer‑Policy HTTP response header field or via HTML element attributes. Sensitive data could include path and query data in the URL, and for internal non‑public applications also the hostname. | Compliant | Referrer-Policy: strict-origin-when-cross-origin set in fancyadmin config/security.neon via http: headers:. | — | +| 3.4.6 | 2 | Verify that the web application uses the frame‑ancestors directive of the Content‑Security‑Policy header field for every HTTP response to ensure that it cannot be embedded by default and that embedding of specific resources is allowed only when necessary. Note that the X‑Frame‑Options header field, although supported by browsers, is obsolete and may not be relied upon. | Compliant | frame-ancestors: 'none' in CSP config. X-Frame-Options: DENY also set as fallback. | — | +| 3.4.7 | 3 | Verify that the Content‑Security‑Policy header field specifies a location to report violations. | | | | +| 3.4.8 | 3 | Verify that all HTTP responses that initiate a document rendering (such as responses with Content‑Type text/html), include the Cross‑Origin‑Opener‑Policy header field with the same‑origin directive or the same‑origin‑allow‑popups directive as required. This prevents attacks that abuse shared access to Window objects, such as tabnabbing and frame counting. | | | | + +## V3.5 Browser Origin Separation + +When accepting a request to sensitive functionality on the server side, the application needs to ensure the request is initiated by the application itself or by a trusted party and has not been forged by an attacker. Sensitive functionality in this context could include accepting form posts for authenticated and non‑authenticated users (such as an authentication request), state‑changing operations, or resource‑demanding functionality (such as data export). The key protections here are browser security policies like Same Origin Policy for JavaScript and also SameSite logic for cookies. Another common protection is the CORS preflight mechanism. This mechanism will be critical for endpoints designed to be called from a different origin, but it can also be a useful request forgery prevention mechanism for endpoints which are not designed to be called from a different origin. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 3.5.1 | 1 | Verify that, if the application does not rely on the CORS preflight mechanism to prevent disallowed cross‑origin requests to use sensitive functionality, these requests are validated to ensure they originate from the application itself. This may be done by using and validating anti‑forgery tokens or requiring extra HTTP header fields that are not CORS‑safelisted request‑header fields. This is to defend against browser‑based request forgery attacks, commonly known as cross‑site request forgery (CSRF). | Compliant | CSRF protection via Nette form addProtection() in BaseFormTrait. All forms automatically include anti-forgery tokens. | — | +| 3.5.2 | 1 | Verify that, if the application relies on the CORS preflight mechanism to prevent disallowed cross‑origin use of sensitive functionality, it is not possible to call the functionality with a request which does not trigger a CORS‑preflight request. This may require checking the values of the 'Origin' and 'Content‑Type'request header fields or using an extra header field that is not a CORS‑safelisted header‑field. | Compliant | Application does not rely on CORS preflight. Uses CSRF tokens (addProtection) and POST methods for state-changing operations. | — | +| 3.5.3 | 1 | Verify that HTTP requests to sensitive functionality use appropriate HTTP methods such as POST, PUT, PATCH, or DELETE, and not methods defined by the HTTP specification as "safe"such as HEAD, OPTIONS, or GET. Alternatively, strict validation of the Sec‑Fetch‑* request header fields can be used to ensure that the request did not originate from an inappropriate cross‑origin call, a navigation request, or a resource load (such as an image source) where this is not expected. | Compliant | Nette forms use POST method. State-changing operations use POST/PUT/DELETE. | — | +| 3.5.4 | 2 | Verify that separate applications are hosted on different hostnames to leverage the restrictions provided by same‑origin policy, including how documents or scripts loaded by one origin can interact with resources from another origin and hostname‑based restrictions on cookies. | Compliant | Single application per hostname. No cross-origin resource sharing needed. | — | +| 3.5.5 | 2 | Verify that messages received by the postMessage interface are discarded if the origin of the message is not trusted, or if the syntax of the message is invalid. | Out of scope | Application does not use postMessage API. | — | +| 3.5.6 | 3 | Verify that JSONP functionality is not enabled anywhere across the application to avoid Cross‑Site Script Inclusion (XSSI) attacks. | | | | +| 3.5.7 | 3 | Verify that data requiring authorization is not included in script resource responses, like JavaScript files, to prevent Cross‑Site Script Inclusion (XSSI) attacks. | | | | +| 3.5.8 | 3 | Verify that authenticated resources (such as images, videos, scripts, and other documents) can be loaded or embedded on behalf of the user only when intended. This can be accomplished by strict validation of the Sec‑Fetch‑* HTTP request header fields to ensure that the request did not originate from an inappropriate cross‑origin call, or by setting a restrictive Cross‑Origin‑Resource‑Policy HTTP response header field to instruct the browser to block returned content. | | | | + +## V3.6 External Resource Integrity + +This section provides guidance for the safe hosting of content on third‑party sites. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 3.6.1 | 3 | Verify that client‑side assets, such as JavaScript libraries, CSS, or web fonts, are only hosted externally (e.g., on a Content Delivery Network) if the resource is static and versioned and Subresource Integrity (SRI) is used to validate the integrity of the asset. If this is not possible, there should be a documented security decision to justify this for each resource. | | | | + +## V3.7 Other Browser Security Considerations + +This section includes various other security controls and modern browser security features required for client‑side browser security. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 3.7.1 | 2 | Verify that the application only uses client‑side technologies which are still supported and considered secure. Examples of technologies which do not meet this requirement include NSAPI plugins, Flash, Shockwave, ActiveX, Silverlight, NACL, or client‑side Java applets. | Compliant | No legacy plugins (Flash, Silverlight, etc.). Modern JS/CSS stack with Vite. | — | +| 3.7.2 | 2 | Verify that the application will only automatically redirect the user to a different hostname or domain (which is not controlled by the application) where the destination appears on an allowlist. | Compliant | Application does not perform automatic redirects to external domains. Nette routing handles internal redirects only. | — | +| 3.7.3 | 3 | Verify that the application shows a notification when the user is being redirected to a URL outside of the application's control, with an option to cancel the navigation. | | | | +| 3.7.4 | 3 | Verify that the application's top‑level domain (e.g., site.tld) is added to the public preload list for HTTP Strict Transport Security (HSTS). This ensures that the use of TLS for the application is built directly into the main browsers, rather than relying only on the Strict‑Transport‑Security response header field. | | | | +| 3.7.5 | 3 | Verify that the application behaves as documented (such as warning the user or blocking access) if the browser used to access the application does not support the expected security features. | | | | + +--- + +**Total requirements in this chapter: 31** +- Level 1: 8 +- Level 2: 11 +- Level 3: 12 diff --git a/ASVS/V04-API-and-Web-Service.md b/ASVS/V04-API-and-Web-Service.md new file mode 100644 index 0000000..2b0d3d2 --- /dev/null +++ b/ASVS/V04-API-and-Web-Service.md @@ -0,0 +1,54 @@ +# V4 API and Web Service + +OWASP Application Security Verification Standard 5.0.0 + +## V4.1 Generic Web Service Security + +This section addresses general web service security considerations and, consequently, basic web service hygiene practices. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 4.1.1 | 1 | Verify that every HTTP response with a message body contains a Content‑Type header field that matches the actual content of the response, including the charset parameter to specify safe character encoding (e.g., UTF‑8, ISO‑8859‑1) according to IANA Media Types, such as "text/", "/+xml" and "/xml". | Compliant | Nette framework handles Content-Type validation. JSON APIs use Nette\Utils\Json. | — | +| 4.1.2 | 2 | Verify that only user‑facing endpoints (intended for manual web‑browser access) automatically redirect from HTTP to HTTPS, while other services or endpoints do not implement transparent redirects. This is to avoid a situation where a client is erroneously sending unencrypted HTTP requests, but since the requests are being automatically redirected to HTTPS, the leakage of sensitive data goes undiscovered. | Compliant | Application uses Nette presenters for user-facing endpoints. No separate machine-to-machine API endpoints exposed to users. | — | +| 4.1.3 | 2 | Verify that any HTTP header field used by the application and set by an intermediary layer, such as a load balancer, a web proxy, or a backend‑for‑frontend service, cannot be overridden by the end‑user. Example headers might include X‑Real‑IP, X‑Forwarded‑*, or X‑User‑ID. | Compliant | Standard HTTP headers used. Custom headers validated by Nette framework. | — | +| 4.1.4 | 3 | Verify that only HTTP methods that are explicitly supported by the application or its API (including OPTIONS during preflight requests) can be used and that unused methods are blocked. | | | | +| 4.1.5 | 3 | Verify that per‑message digital signatures are used to provide additional assurance on top of transport protections for requests or transactions which are highly sensitive or which traverse a number of systems. | | | | + +## V4.2 HTTP Message Structure Validation + +This section explains how the structure and header fields of an HTTP message should be validated to prevent attacks such as request smuggling, response splitting, header injection, and denial of service via overly long HTTP messages. These requirements are relevant for general HTTP message processing and generation, but are es‑ pecially important when converting HTTP messages between different HTTP versions. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 4.2.1 | 2 | Verify that all application components (including load balancers, firewalls, and application servers) determine boundaries of incoming HTTP messages using the appropriate mechanism for the HTTP version to prevent HTTP request smuggling. In HTTP/1.x, if a Transfer‑Encoding header field is present, the Content‑Length header must be ignored per RFC 2616. When using HTTP/2 or HTTP/3, if a Content‑Length header field is present, the receiver must ensure that it is consistent with the length of the DATA frames. | Compliant | Single Nette application. No mixed HTTP method interpretation. Nette router consistently handles methods. | — | +| 4.2.2 | 3 | Verify that when generating HTTP messages, the Content‑Length header field does not conflict with the length of the content as determined by the framing of the HTTP protocol, in order to prevent request smuggling attacks. | | | | +| 4.2.3 | 3 | Verify that the application does not send nor accept HTTP/2 or HTTP/3 messages with connection‑specific header fields such as Transfer‑Encoding to prevent response splitting and header injection attacks. | | | | +| 4.2.4 | 3 | Verify that the application only accepts HTTP/2 and HTTP/3 requests where the header fields and values do not contain any CR (\r), LF (\n), or CRLF (\r\n) sequences, to prevent header injection attacks. | | | | +| 4.2.5 | 3 | Verify that, if the application (backend or frontend) builds and sends requests, it uses validation, sanitization, or other mechanisms to avoid creating URIs (such as for API calls) or HTTP request header fields (such as Authorization or Cookie), which are too long to be accepted by the receiving component. This could cause a denial of service, such as when sending an overly long request (e.g., a long cookie header field), which results in the server always responding with an error status. | | | | + +## V4.3 GraphQL + +GraphQL is becoming more common as a way of creating data‑rich clients that are not tightly coupled to a variety of backend services. This section covers security considerations for GraphQL. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 4.3.1 | 2 | Verify that a query allowlist, depth limiting, amount limiting, or query cost analysis is used to prevent GraphQL or data layer expression Denial of Service (DoS) as a result of expensive, nested queries. | Out of scope | Application does not use GraphQL. | — | +| 4.3.2 | 2 | Verify that GraphQL introspection queries are disabled in the production environment unless the GraphQL API is meant to be used by other parties. | Out of scope | Application does not use GraphQL. | — | + +## V4.4 WebSocket + +WebSocket is a communications protocol that provides a simultaneous two‑way communication channel over a single TCP connection. It was standardized by the IETF as RFC 6455 in 2011 and is distinct from HTTP, even though it is designed to work over HTTP ports 443 and 80. This section provides key security requirements to prevent attacks related to communication security and session management that specifically exploit this real‑time communication channel. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 4.4.1 | 1 | Verify that WebSocket over TLS (WSS) is used for all WebSocket connections. | Out of scope | WebSocket usage is project-specific, not part of fancyadmin framework. | — | +| 4.4.2 | 2 | Verify that, during the initial HTTP WebSocket handshake, the Origin header field is checked against a list of origins allowed for the application. | Out of scope | WebSocket usage is project-specific, not part of fancyadmin framework. | — | +| 4.4.3 | 2 | Verify that, if the application's standard session management cannot be used, dedicated tokens are being used for this, which comply with the relevant Session Management security requirements. | Out of scope | WebSocket usage is project-specific, not part of fancyadmin framework. | — | +| 4.4.4 | 2 | Verify that dedicated WebSocket session management tokens are initially obtained or validated through the previously authenticated HTTPS session when transitioning an existing HTTPS session to a WebSocket channel. | Out of scope | WebSocket usage is project-specific, not part of fancyadmin framework. | — | + +--- + +**Total requirements in this chapter: 16** +- Level 1: 2 +- Level 2: 8 +- Level 3: 6 diff --git a/ASVS/V05-File-Handling.md b/ASVS/V05-File-Handling.md new file mode 100644 index 0000000..c0daf73 --- /dev/null +++ b/ASVS/V05-File-Handling.md @@ -0,0 +1,51 @@ +# V5 File Handling + +OWASP Application Security Verification Standard 5.0.0 + +## V5.1 File Handling Documentation + +This section includes a requirement to document the expected characteristics of files accepted by the application, as a necessary precondition for developing and verifying relevant security checks. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 5.1.1 | 2 | Verify that the documentation defines the permitted file types, expected file extensions, and maximum size (including unpacked size) for each upload feature. Additionally, ensure that the documentation specifies how files are made safe for end‑users to download and process, such as how the application behaves when a malicious file is detected. | Partial | No formal documentation of permitted file types, sizes, or frequency. Validation depends on per-project configuration. | Per-project: document allowed MIME types (reference `FileUploadRules::ALLOWED_MIME_TYPES`), file extensions, and max sizes per upload feature. | + +## V5.2 File Upload and Content + +File upload functionality is a primary source of untrusted files. This section outlines the require‑ ments for ensuring that the presence, volume, or content of these files cannot harm the applica‑ tion. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 5.2.1 | 1 | Verify that the application will only accept files of a size which it can process without causing a loss of performance or a denial of service attack. | Compliant | BaseForm asserts every UploadControl has Form::MaxFileSize rule (throws LogicException if missing). Summernote handler checks size against FileUploadRules::MAX_FILE_SIZE. | — | +| 5.2.2 | 1 | Verify that when the application accepts a file, either on its own or within an archive such as a zip file, it checks if the file extension matches an expected file extension and validates that the contents correspond to the type represented by the extension. This includes, but is not limited to, checking the initial 'magic bytes', performing image re‑writing, and using specialized libraries for file content validation. For L1, this can focus just on files which are used to make specific business or security decisions. For L2 and up, this must apply to all files being accepted. | Compliant | BaseForm asserts every UploadControl has Form::MimeType or Form::Image rule (throws LogicException if missing). Nette validates MIME via finfo magic bytes. Summernote handler checks against FileUploadRules::ALLOWED_MIME_TYPES. | — | +| 5.2.3 | 2 | Verify that the application checks compressed files (e.g., zip, gz, docx, odt) against maximum allowed uncompressed size and against maximum number of files before uncompressing the file. | Partial | No compressed file (zip bomb) validation. Needs implementation if compressed files are accepted. | Per-project: if accepting .zip/.docx/.odt uploads, add decompression size and file count limits before extraction. | +| 5.2.4 | 3 | Verify that a file size quota and maximum number of files per user are enforced to ensure that a single user cannot fill up the storage with too many files, or excessively large files. | | | | +| 5.2.5 | 3 | Verify that the application does not allow uploading compressed files containing symlinks unless this is specifically required (in which case it will be necessary to enforce an allowlist of the files that can be symlinked to). | | | | +| 5.2.6 | 3 | Verify that the application rejects uploaded images with a pixel size larger than the maximum allowed, to prevent pixel flood attacks. | | | | + +## V5.3 File Storage + +This section includes requirements to prevent files from being inappropriately executed after upload, to detect dangerous content, and to avoid untrusted data being used to control where files are being stored. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 5.3.1 | 1 | Verify that files uploaded or generated by untrusted input and stored in a public folder, are not executed as server‑side program code when accessed directly with an HTTP request. | Compliant | Nginx snippet provided in config/nginx/uploads.conf blocks PHP execution in upload directory. Private files stored outside webroot. Filenames randomized with hash. | Include `vendor/adt/fancyadmin/config/nginx/uploads.conf` in your nginx server block. Adjust the location path to match your FileListener dataDir. | +| 5.3.2 | 1 | Verify that when the application creates file paths for file operations, instead of user‑submitted filenames, it uses internally generated or trusted data, or if user‑submitted filenames or file metadata must be used, strict validation and sanitization must be applied. This is to protect against path traversal, local or remote file inclusion (LFI, RFI), and server‑side request forgery (SSRF) attacks. | Compliant | Filenames are generated by adt/files with ID-based directory structure and random hash. Original filename is webalized and sanitized. No user input in filesystem paths. | — | +| 5.3.3 | 3 | Verify that server‑side file processing, such as file decompression, ignores user‑provided path information to prevent vulnerabilities such as zip slip. | | | | + +## V5.4 File Download + +This section contains requirements to mitigate risks when serving files to be downloaded, including path traversal and injection attacks. This also includes making sure they don't contain dangerous content. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 5.4.1 | 2 | Verify that the application validates or ignores user‑submitted filenames, including in a JSON, JSONP, or URL parameter and specifies a filename in the Content‑Disposition header field in the response. | Partial | No explicit metadata stripping. Original EXIF data preserved in uploaded images. | Per-project: strip EXIF metadata from uploaded images using PHP `gd` (re-process via `imagecreatefrom*`) or `imagick`. | +| 5.4.2 | 2 | Verify that file names served (e.g., in HTTP response header fields or email attachments) are encoded or sanitized (e.g., following RFC 6266) to preserve document structure and prevent injection attacks. | Compliant | Filenames served are generated by the system (webalized + random hash). No user-controlled filename in HTTP responses. | — | +| 5.4.3 | 2 | Verify that files obtained from untrusted sources are scanned by antivirus scanners to prevent serving of known malicious content. | Partial | No antivirus scanning of uploaded files. Needs implementation (e.g., ClamAV integration). | Per-project: integrate ClamAV scanning via PHP socket (`/var/run/clamav/clamd.ctl`) before persisting uploaded files. | + +--- + +**Total requirements in this chapter: 13** +- Level 1: 4 +- Level 2: 5 +- Level 3: 4 diff --git a/ASVS/V06-Authentication.md b/ASVS/V06-Authentication.md new file mode 100644 index 0000000..85f9e33 --- /dev/null +++ b/ASVS/V06-Authentication.md @@ -0,0 +1,113 @@ +# V6 Authentication + +OWASP Application Security Verification Standard 5.0.0 + +## V6.1 Authentication Documentation + +This section contains requirements detailing the authentication documentation that should be main‑ tained for an application. This is crucial for implementing and assessing how the relevant authenti‑ cation controls should be configured. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 6.1.1 | 1 | Verify that application documentation defines how controls such as rate limiting, anti‑automation, and adaptive response, are used to defend against attacks such as credential stuffing and password brute force. The documentation must make clear how these controls are configured and prevent malicious account lockout. | Partial | Rate limiting configured via setLoginAttemptProtection(). Documentation of controls not yet formalized in ASVS format. | Per-project: document `setLoginAttemptProtection($maxAttempts, $timeout)` values and rationale in project security docs. | +| 6.1.2 | 2 | Verify that a list of context‑specific words is documented in order to prevent their use in passwords. The list could include permutations of organization names, product names, system identifiers, project codenames, department or role names, and similar. | Partial | Context-specific word list not yet documented or implemented. | Per-project: create context-specific word list (org name, product name, domain) for password deny list. | +| 6.1.3 | 2 | Verify that, if the application includes multiple authentication pathways, these are all documented together with the security controls and authentication strength which must be consistently enforced across them. | Compliant | Single authentication pathway (email/username + password). No multiple pathways. | — | + +## V6.2 Password Security + +Passwords, called "Memorized Secrets"by NIST SP 800‑63, include passwords, passphrases, PINs, unlock patterns, and picking the correct kitten or another image element. They are generally con‑ sidered "something you know"and are often used as a single‑factor authentication mechanism. As such, this section contains requirements for making sure that passwords are created and handled securely. Most of the requirements are L1 as they are most important at that level. From L2 on‑ wards, multi‑factor authentication mechanisms are required, where passwords may be one of those factors. The requirements in this section mostly relate to § 5.1.1.2 of NIST's Guidance. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 6.2.1 | 1 | Verify that user set passwords are at least 8 characters in length although a minimum of 15 characters is strongly recommended. | Compliant | Configurable password policy stored in Configuration table. Minimum length enforced (default 8, configurable). NewPasswordFormTrait validates against policy. | — | +| 6.2.2 | 1 | Verify that users can change their password. | Compliant | Password change available via NewPassword form (Account page / admin-initiated reset). | — | +| 6.2.3 | 1 | Verify that password change functionality requires the user's current and new password. | Compliant | ChangePasswordFormTrait on Account page requires current password + new password. Password recovery via email token (NewPasswordFormTrait) does not require current password by design — recovery flow for users who lost access. | — | +| 6.2.4 | 1 | Verify that passwords submitted during account registration or password change are checked against an available set of, at least, the top 3000 passwords which match the application's password policy, e.g. minimum length. | Compliant | BreachedPasswordChecker checks against local top 10k common passwords list (resources/common-passwords.txt). Integrated into NewPasswordFormTrait::validateForm(). | — | +| 6.2.5 | 1 | Verify that passwords of any composition can be used, without rules limiting the type of characters permitted. There must be no requirement for a minimum number of upper or lower case characters, numbers, or special characters. | Compliant | Password policy is disabled by default (enabled: false in Configuration). No composition rules enforced — passwords of any composition accepted. Only minimum length (8 chars) required at form level. | — | +| 6.2.6 | 1 | Verify that password input fields use type=password to mask the entry. Applications may allow the user to temporarily view the entire masked password, or the last typed character of the password. | Compliant | Password input fields use type=password. Nette forms default behavior. | — | +| 6.2.7 | 1 | Verify that "paste"functionality, browser password helpers, and external password managers are permitted. | Compliant | No restrictions on paste functionality or password managers. Standard HTML inputs. | — | +| 6.2.8 | 1 | Verify that the application verifies the user's password exactly as received from the user, without any modifications such as truncation or case transformation. | Compliant | Passwords verified as-is using password_verify(). No truncation or case transformation. | — | +| 6.2.9 | 2 | Verify that passwords of at least 64 characters are permitted. | Compliant | No maximum password length limit enforced by the application. | — | +| 6.2.10 | 2 | Verify that a user's password stays valid until it is discovered to be compromised or the user rotates it. The application must not require periodic credential rotation. | Compliant | No periodic credential rotation required. Passwords valid until user changes them. | — | +| 6.2.11 | 2 | Verify that the documented list of context specific words is used to prevent easy to guess passwords being created. | Partial | Context-specific word list not yet implemented. | Pending fancyadmin: add `setContextWordList(array)` to `BreachedPasswordChecker`. Per-project: configure via DI `setup:` in `common.neon`. | +| 6.2.12 | 2 | Verify that passwords submitted during account registration or password changes are checked against a set of breached passwords. | Compliant | BreachedPasswordChecker checks against local top 10k common passwords list and HaveIBeenPwned Pwned Passwords API (k-Anonymity, 500ms timeout, fail-open). Checked during password change in NewPasswordFormTrait::validateForm(). | — | + +## V6.3 General Authentication Security + +This section contains general requirements for the security of authentication mechanisms as well as setting out the different expectations for levels. L2 applications must force the use of multi‑factor authentication (MFA). L3 applications must use hardware‑based authentication, performed in an at‑ tested and trusted execution environment (TEE). This could include device‑bound passkeys, eIDAS Level of Assurance (LoA) High enforced authenticators, authenticators with NIST Authenticator As‑ surance Level 3 (AAL3) assurance, or an equivalent mechanism. While this is a relatively aggressive stance on MFA, it is critical to raise the bar around this to protect users, and any attempt to relax these requirements should be accompanied by a clear plan on how the risks around authentication will be mitigated, taking into account NIST's guidance and research on the topic. Note that at the time of release, NIST SP 800‑63 considers email as not acceptable as an authentication mechanism (archived copy). The requirements in this section relate to a variety of sections of NIST's Guidance, including: § 4.2.1, § 4.3.1, § 5.2.2, and § 6.1.2. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 6.3.1 | 1 | Verify that controls to prevent attacks such as credential stuffing and password brute force are implemented according to the application's security documentation. | Compliant | IP-based rate limiting via DoctrineAuthenticator. Configurable maxLoginAttempts and loginAttemptTimeout. | — | +| 6.3.2 | 1 | Verify that default user accounts (e.g., "root", "admin", or "sa") are not present in the application or are disabled. | Compliant | No default user accounts. All accounts created through application flows. | — | +| 6.3.3 | 2 | Verify that either a multi‑factor authentication mechanism or a combination of single‑factor authentication mechanisms, must be used in order to access the application. For L3, one of the factors must be a hardware‑based authentication mechanism which provides compromise and impersonation resistance against phishing attacks while verifying the intent to authenticate by requiring a user‑initiated action (such as a button press on a FIDO hardware key or a mobile phone). Relaxing any of the considerations in this requirement requires a fully documented rationale and a comprehensive set of mitigating controls. | Partial | MFA not yet implemented. Single-factor authentication (password) only. MFA is required for L2 compliance. | Pending fancyadmin: implement TOTP MFA. `IdentityTrait` already uses `DoctrineAuthenticator\OTP\IdentityTrait` as foundation. | +| 6.3.4 | 2 | Verify that, if the application includes multiple authentication pathways, there are no undocumented pathways and that security controls and authentication strength are enforced consistently. | Compliant | Single authentication pathway (email + password). No undocumented pathways. | — | +| 6.3.5 | 3 | Verify that users are notified of suspicious authentication attempts (successful or unsuccessful). This may include authentication attempts from an unusual location or client, partially successful authentication (only one of multiple factors), an authentication attempt after a long period of inactivity or a successful authentication after several unsuccessful attempts. | | | | +| 6.3.6 | 3 | Verify that email is not used as either a single‑factor or multi‑factor authentication mechanism. | | | | +| 6.3.7 | 3 | Verify that users are notified after updates to authentication details, such as credential resets or modification of the username or email address. | | | | +| 6.3.8 | 3 | Verify that valid users cannot be deduced from failed authentication challenges, such as by basing on error messages, HTTP response codes, or different response times. Registration and forgot password functionality must also have this protection. | Compliant | Generic error message 'wrongEmailOrPassword' for all auth failures. No user enumeration via error messages. | — | + +## V6.4 Authentication Factor Lifecycle and Recovery + +Authentication factors may include passwords, soft tokens, hardware tokens, and biometric devices. Securely handling the lifecycle of these mechanisms is critical to the security of an application, and this section includes requirements related to this. The requirements in this section mostly relate to § 5.1.1.2 or § 6.1.2.3 of NIST's Guidance. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 6.4.1 | 1 | Verify that system generated initial passwords or activation codes are securely randomly generated, follow the existing password policy, and expire after a short period of time or after they are initially used. These initial secrets must not be permitted to become the long term password. | Compliant | Initial passwords set via secure email link with token (OnetimeTokenService). Token expires. User must set their own password. | — | +| 6.4.2 | 1 | Verify that password hints or knowledge‑based authentication (so‑called "secret questions") are not present. | Compliant | No password hints or secret questions in the application. | — | +| 6.4.3 | 2 | Verify that a secure process for resetting a forgotten password is implemented, that does not bypass any enabled multi‑factor authentication mechanisms. | Compliant | Password reset via secure email link using OnetimeTokenService. Token-based recovery flow. | — | +| 6.4.4 | 2 | Verify that if a multi‑factor authentication factor is lost, evidence of identity proofing is performed at the same level as during enrollment. | Out of scope | MFA not yet implemented. | — | +| 6.4.5 | 3 | Verify that renewal instructions for authentication mechanisms which expire are sent with enough time to be carried out before the old authentication mechanism expires, configuring automated reminders if necessary. | | | | +| 6.4.6 | 3 | Verify that administrative users can initiate the password reset process for the user, but that this does not allow them to change or choose the user's password. This prevents a situation where they know the user's password. | Compliant | Admin initiates password reset which sends email to user. Admin cannot set or see the password. | — | + +## V6.5 General Multi‑factor authentication requirements + +This section provides general guidance that will be relevant to various different multi‑factor authen‑ tication methods. The mechanisms include: • Lookup Secrets • Time based One‑time Passwords (TOTPs) • Out‑of‑Band mechanisms Lookup secrets are pre‑generated lists of secret codes, similar to Transaction Authorization Num‑ bers (TAN), social media recovery codes, or a grid containing a set of random values. This type of authentication mechanism is considered "something you have"because the codes are deliberately not memorable so will need to be stored somewhere. Time based One‑time Passwords (TOTPs) are physical or soft tokens that display a continually changing pseudo‑random one‑time challenge. This type of authentication mechanism is considered "something you have". Multi‑factor TOTPs are similar to single‑factor TOTPs, but require a valid PIN code, biometric unlocking, USB insertion or NFC pairing, or some additional value (such as transaction signing calculators) to be entered to create the final One‑time Password (OTP). + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 6.5.1 | 2 | Verify that lookup secrets, out‑of‑band authentication requests or codes, and time‑based one‑time passwords (TOTPs) are only successfully usable once. | Out of scope | MFA not yet implemented. These requirements apply when MFA is added. | — | +| 6.5.2 | 2 | Verify that, when being stored in the application's backend, lookup secrets with less than 112 bits of entropy (19 random alphanumeric characters or 34 random digits) are hashed with an approved password storage hashing algorithm that incorporates a 32‑bit random salt. A standard hash function can be used if the secret has 112 bits of entropy or more. | Out of scope | MFA not yet implemented. These requirements apply when MFA is added. | — | +| 6.5.3 | 2 | Verify that lookup secrets, out‑of‑band authentication code, and time‑based one‑time password seeds, are generated using a Cryptographically Secure Pseudorandom Number Generator (CSPRNG) to avoid predictable values. | Out of scope | MFA not yet implemented. These requirements apply when MFA is added. | — | +| 6.5.4 | 2 | Verify that lookup secrets and out‑of‑band authentication codes have a minimum of 20 bits of entropy (typically 4 random alphanumeric characters or 6 random digits is sufficient). | Out of scope | MFA not yet implemented. These requirements apply when MFA is added. | — | +| 6.5.5 | 2 | Verify that out‑of‑band authentication requests, codes, or tokens, as well as time‑based one‑time passwords (TOTPs) have a defined lifetime. Out of band requests must have a maximum lifetime of 10 minutes and for TOTP a maximum lifetime of 30 seconds. | Out of scope | MFA not yet implemented. These requirements apply when MFA is added. | — | +| 6.5.6 | 3 | Verify that any authentication factor (including physical devices) can be revoked in case of theft or other loss. | | | | +| 6.5.7 | 3 | Verify that biometric authentication mechanisms are only used as secondary factors together with either something you have or something you know. | | | | +| 6.5.8 | 3 | Verify that time‑based one‑time passwords (TOTPs) are checked based on a time source from a trusted service and not from an untrusted or client provided time. | | | | + +## V6.6 Out‑of‑Band authentication mechanisms + +This usually involves the authentication server communicating with a physical device over a secure secondary channel. For example, sending push notifications to mobile devices. This type of authen‑ tication mechanism is considered "something you have". Unsafe out‑of‑band authentication mechanisms such as e‑mail and VOIP are not permitted. PSTN and SMS authentication are currently considered to be "restricted"authentication mechanisms by + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 6.6.1 | 2 | Verify that authentication mechanisms using the Public Switched Telephone Network (PSTN) to deliver One‑time Passwords (OTPs) via phone or SMS are offered only when the phone number has previously been validated, alternate stronger methods (such as Time based One‑time Passwords) are also offered, and the service provides information on their security risks to users. For L3 applications, phone and SMS must not be available as options. | Out of scope | MFA not yet implemented. These requirements apply when MFA is added. | — | +| 6.6.2 | 2 | Verify that out‑of‑band authentication requests, codes, or tokens are bound to the original authentication request for which they were generated and are not usable for a previous or subsequent one. | Out of scope | MFA not yet implemented. These requirements apply when MFA is added. | — | +| 6.6.3 | 2 | Verify that a code based out‑of‑band authentication mechanism is protected against brute force attacks by using rate limiting. Consider also using a code with at least 64 bits of entropy. | Out of scope | MFA not yet implemented. These requirements apply when MFA is added. | — | +| 6.6.4 | 3 | Verify that, where push notifications are used for multi‑factor authentication, rate limiting is used to prevent push bombing attacks. Number matching may also mitigate this risk. | | | | + +## V6.7 Cryptographic authentication mechanism + +Cryptographic authentication mechanisms include smart cards or FIDO keys, where the user has to plug in or pair the cryptographic device to the computer to complete authentication. The authenti‑ cation server will send a challenge nonce to the cryptographic device or software, and the device or software calculates a response based upon a securely stored cryptographic key. The requirements in this section provide implementation‑specific guidance for these mechanisms, with guidance on cryptographic algorithms being covered in the "Cryptography"chapter. Where shared or secret keys are used for cryptographic authentication, these should be stored using the same mechanisms as other system secrets, as documented in the "Secret Management"section in the "Configuration"chapter. The requirements in this section mostly relate to § 5.1.7.2 of NIST's Guidance. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 6.7.1 | 3 | Verify that the certificates used to verify cryptographic authentication assertions are stored in a way protects them from modification. | | | | +| 6.7.2 | 3 | Verify that the challenge nonce is at least 64 bits in length, and statistically unique or unique over the lifetime of the cryptographic device. | | | | + +## V6.8 Authentication with an Identity Provider + +Identity Providers (IdPs) provide federated identity for users. Users will often have more than one identity with multiple IdPs, such as an enterprise identity using Azure AD, Okta, Ping Identity, or Google, or consumer identity using Facebook, Twitter, Google, or WeChat, to name just a few com‑ mon alternatives. This list is not an endorsement of these companies or services, but simply an encouragement for developers to consider the reality that many users have many established identi‑ ties. Organizations should consider integrating with existing user identities, as per the risk profile of the IdP's strength of identity proofing. For example, it is unlikely a government organization would accept a social media identity as a login for sensitive systems, as it is easy to create fake or throw‑ away identities, whereas a mobile game company may well need to integrate with major social media platforms to grow their active player base. Secure use of external identity providers requires careful configuration and verification to prevent identity spoofing or forged assertions. This section provides requirements to address these risks. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 6.8.1 | 2 | Verify that, if the application supports multiple identity providers (IdPs), the user's identity cannot be spoofed via another supported identity provider (eg. by using the same user identifier). The standard mitigation would be for the application to register and identify the user using a combination of the IdP ID (serving as a namespace) and the user's ID in the IdP. | Out of scope | Application does not use external identity providers. | — | +| 6.8.2 | 2 | Verify that the presence and integrity of digital signatures on authentication assertions (for example on JWTs or SAML assertions) are always validated, rejecting any assertions that are unsigned or have invalid signatures. | Out of scope | Application does not use external identity providers. | — | +| 6.8.3 | 2 | Verify that SAML assertions are uniquely processed and used only once within the validity period to prevent replay attacks. | Out of scope | Application does not use external identity providers. | — | +| 6.8.4 | 2 | Verify that, if an application uses a separate Identity Provider (IdP) and expects specific authentication strength, methods, or recentness for specific functions, the application verifies this using the information returned by the IdP. For example, if OIDC is used, this might be achieved by validating ID Token claims such as 'acr', 'amr', and 'auth_time'(if present). If the IdP does not provide this information, the application must have a documented fallback approach that assumes that the minimum strength authentication mechanism was used (for example, single‑factor authentication using username and password). | Out of scope | Application does not use external identity providers. | — | + +--- + +**Total requirements in this chapter: 47** +- Level 1: 13 +- Level 2: 22 +- Level 3: 12 diff --git a/ASVS/V07-Session-Management.md b/ASVS/V07-Session-Management.md new file mode 100644 index 0000000..48a1c16 --- /dev/null +++ b/ASVS/V07-Session-Management.md @@ -0,0 +1,71 @@ +# V7 Session Management + +OWASP Application Security Verification Standard 5.0.0 + +## V7.1 Session Management Documentation + +There is no single pattern that suits all applications. Therefore, it is not feasible to define universal boundaries and limits that suit all cases. A risk analysis with documented security decisions related to session handling must be conducted as a prerequisite to implementation and testing. This ensures that the session management system is tailored to the specific requirements of the application. Regardless of whether a stateful or "stateless"session mechanism is chosen, the analysis must be complete and documented to demonstrate that the selected solution is capable of satisfying all rel‑ evant security requirements. Interaction with any Single Sign‑on (SSO) mechanisms in use should also be considered. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 7.1.1 | 2 | Verify that the user's session inactivity timeout and absolute maximum session lifetime are documented, are appropriate in combination with other controls, and that the documentation includes justification for any deviations from NIST SP 800‑63B re‑authentication requirements. | Partial | Inactivity timeout: 14 days (configurable via SessionExpirationCallback). Absolute lifetime: same as inactivity (extended on each request). Documentation not yet formalized. | Per-project: document `sessionExpirationMinutes` values in `password.policy.admin`/`password.policy.backoffice` Configuration entities with justification. | +| 7.1.2 | 2 | Verify that the documentation defines how many concurrent (parallel) sessions are allowed for one account as well as the intended behaviors and actions to be taken when the maximum number of active sessions is reached. | Partial | No limit on concurrent sessions. Documentation not yet formalized. | Per-project: document concurrent session policy. Currently unlimited — decide if limit needed and configure in `DoctrineAuthenticator`. | +| 7.1.3 | 2 | Verify that all systems that create and manage user sessions as part of a federated identity management ecosystem (such as SSO systems) are documented along with controls to coordinate session lifetimes, termination, and any other conditions that require re‑authentication. | Out of scope | No federated identity management / SSO in use. | — | + +## V7.2 Fundamental Session Management Security + +This section satisfies the essential requirements of secure sessions by verifying that session tokens are securely generated and validated. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 7.2.1 | 1 | Verify that the application performs all session token verification using a trusted, backend service. | Compliant | All session token verification in DoctrineAuthenticator backend. Token looked up via SHA-256 hash in database. | — | +| 7.2.2 | 1 | Verify that the application uses either self‑contained or reference tokens that are dynamically generated for session management, i.e. not using static API secrets and keys. | Compliant | Reference tokens dynamically generated per session via Nette\Utils\Random::generate(32). | — | +| 7.2.3 | 1 | Verify that if reference tokens are used to represent user sessions, they are unique and generated using a cryptographically secure pseudo‑random number generator (CSPRNG) and possess at least 128 bits of entropy. | Compliant | 32-character random tokens generated via CSPRNG (Random::generate). 128+ bits of entropy. | — | +| 7.2.4 | 1 | Verify that the application generates a new session token on user authentication, including re‑authentication, and terminates the current session token. | Compliant | New session token generated on each authentication via sleepIdentity(). Previous token not reused. | — | + +## V7.3 Session Timeout + +Session timeout mechanisms serve to minimize the window of opportunity for session hijacking and other forms of session abuse. Timeouts must satisfy documented security decisions. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 7.3.1 | 2 | Verify that there is an inactivity timeout such that re‑authentication is enforced according to risk analysis and documented security decisions. | Compliant | Session inactivity managed via validUntil on StorageEntity. Extended on each request. Configurable expiration (default 14 days). | — | +| 7.3.2 | 2 | Verify that there is an absolute maximum session lifetime such that re‑authentication is enforced according to risk analysis and documented security decisions. | Compliant | Absolute session lifetime via validUntil field. SessionExpirationCallback allows per-identity expiration. | — | + +## V7.4 Session Termination + +Session termination may be handled either by the application itself or by the SSO provider if the SSO provider is handling session management instead of the application. It may be necessary to decide whether the SSO provider is in scope when considering the requirements in this section as some may be controlled by the provider. Session termination should result in requiring re‑authentication and be effective across the applica‑ tion, federated login (if present), and any relying parties. For stateful session mechanisms, termination typically involves invalidating the session on the back‑ end. In the case of self‑contained tokens, additional measures are required to revoke or block these tokens, as they may otherwise remain valid until expiration. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 7.4.1 | 1 | Verify that when session termination is triggered (such as logout or expiration), the application disallows any further use of the session. For reference tokens or stateful sessions, this means invalidating the session data at the application backend. Applications using self‑contained tokens will need a solution such as maintaining a list of terminated tokens, disallowing tokens produced before a per‑user date and time or rotating a per‑user signing key. | Compliant | Logout sets validUntil to now via clearIdentity(). Token invalidated in database. Further use blocked by validUntil check in wakeupIdentity(). | — | +| 7.4.2 | 1 | Verify that the application terminates all active sessions when a user account is disabled or deleted (such as an employee leaving the company). | Compliant | clearIdentity(objectId) invalidates all sessions for a user by setting validUntil to now. | — | +| 7.4.3 | 2 | Verify that the application gives the option to terminate all other active sessions after a successful change or removal of any authentication factor (including password change via reset or recovery and, if present, an MFA settings update). | Compliant | Password change in NewPasswordFormTrait calls clearIdentity() which invalidates all sessions, then creates a new session for the current user. 'Logout all devices' also available on Account page. | — | +| 7.4.4 | 2 | Verify that all pages that require authentication have easy and visible access to logout functionality. | Compliant | Logout button in profile dropdown menu and side menu, visible on all authenticated pages. | — | +| 7.4.5 | 2 | Verify that application administrators are able to terminate active sessions for an individual user or for all users. | Compliant | Admin can manage sessions via clearIdentity(objectId). clearSession(sessionId) for individual session termination. | — | + +## V7.5 Defenses Against Session Abuse + +This section provides requirements to mitigate the risk posed by active sessions that are either hi‑ jacked or abused through vectors that rely on the existence and capabilities of active user sessions. For example, using malicious content execution to force an authenticated victim browser to perform an action using the victim's session. Note that the level‑specific guidance in the "Authentication"chapter should be taken into account when considering requirements in this section. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 7.5.1 | 2 | Verify that the application requires full re‑authentication before allowing modifications to sensitive account attributes which may affect authentication such as email address, phone number, MFA configuration, or other information used in account recovery. | Partial | No re-authentication required before changing email or other sensitive attributes. | Pending fancyadmin: add re-authentication dialog (password confirmation) before sensitive changes (email, password). Reference `ChangePasswordFormTrait`. | +| 7.5.2 | 2 | Verify that users are able to view and (having authenticated again with at least one factor) terminate any or all currently active sessions. | Compliant | Active sessions visible on Account page with IP, User-Agent, creation date, validity. Users can terminate individual sessions via clearSession(). | — | +| 7.5.3 | 3 | Verify that the application requires further authentication with at least one factor or secondary verification before performing highly sensitive transactions or operations. | | | | + +## V7.6 Federated Re‑authentication + +This section relates to those writing Relying Party (RP) or Identity Provider (IdP) code. These re‑ quirements are derived from the NIST SP 800‑63C for Federation & Assertions. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 7.6.1 | 2 | Verify that session lifetime and termination between Relying Parties (RPs) and Identity Providers (IdPs) behave as documented, requiring re‑authentication as necessary such as when the maximum time between IdP authentication events is reached. | Out of scope | No federated identity management / SSO in use. | — | +| 7.6.2 | 2 | Verify that creation of a session requires either the user's consent or an explicit action, preventing the creation of new application sessions without user interaction. | Compliant | Session is only created after explicit user login action (form submission with credentials). | — | + +--- + +**Total requirements in this chapter: 19** +- Level 1: 6 +- Level 2: 12 +- Level 3: 1 diff --git a/ASVS/V08-Authorization.md b/ASVS/V08-Authorization.md new file mode 100644 index 0000000..1ce8377 --- /dev/null +++ b/ASVS/V08-Authorization.md @@ -0,0 +1,51 @@ +# V8 Authorization + +OWASP Application Security Verification Standard 5.0.0 + +## V8.1 Authorization Documentation + +Comprehensive authorization documentation is essential to ensure that security decisions are con‑ sistently applied, auditable, and aligned with organizational policies. This reduces the risk of unau‑ thorized access by making security requirements clear and actionable for developers, administra‑ tors, and testers. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 8.1.1 | 1 | Verify that authorization documentation defines rules for restricting function‑level and data‑specific access based on consumer permissions and resource attributes. | Partial | ACL resources and roles documented in code. No formal authorization documentation. | Per-project: document all `AclResource` entries and which `AclRole` has access. Use `Permission` class as reference. | +| 8.1.2 | 2 | Verify that authorization documentation defines rules for field‑level access restrictions (both read and write) based on consumer permissions and resource attributes. Note that these rules might depend on other attribute values of the relevant data object, such as state or status. | Partial | Field-level access not formally documented. ACL handles resource-level access. | Per-project: document field-level access restrictions per `AclRole` for sensitive entity fields. | +| 8.1.3 | 3 | Verify that the application's documentation defines the environmental and contextual attributes (including but not limited to, time of day, user location, IP address, or device) that are used in the application to make security decisions, including those pertaining to authentication and authorization. | | | | +| 8.1.4 | 3 | Verify that authentication and authorization documentation defines how environmental and contextual factors are used in decision‑making, in addition to function‑level, data‑specific, and field‑level authorization. This should include the attributes evaluated, thresholds for risk, and actions taken (e.g., allow, challenge, deny, step‑up authentication). | | | | + +## V8.2 General Authorization Design + +Implementing granular authorization controls at the function, data, and field levels ensures that con‑ sumers can access only what has been explicitly granted to them. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 8.2.1 | 1 | Verify that the application ensures that function‑level access is restricted to consumers with explicit permissions. | Compliant | Nette Security with ACL-based authorization. Permission checks via SecurityUser and AclResource system. | — | +| 8.2.2 | 1 | Verify that the application ensures that data‑specific access is restricted to consumers with explicit permissions to specific data items to mitigate insecure direct object reference (IDOR) and broken object level authorization (BOLA). | Compliant | Doctrine security filters enforce data-specific access control per Account (tenant). Users only see data belonging to their Account. | — | +| 8.2.3 | 2 | Verify that the application ensures that field‑level access is restricted to consumers with explicit permissions to specific fields to mitigate broken object property level authorization (BOPLA). | Partial | Field-level access control not systematically implemented. ACL works at resource/action level. | Pending fancyadmin: implement field-level ACL. Currently `#[SecurityCheckAttribute]` and `Permission` work at resource/action level only. | +| 8.2.4 | 3 | Verify that adaptive security controls based on a consumer's environmental and contextual attributes (such as time of day, location, IP address, or device) are implemented for authentication and authorization decisions, as defined in the application's documentation. These controls must be applied when the consumer tries to start a new session and also during an existing session. | | | | + +## V8.3 Operation Level Authorization + +The immediate application of authorization changes in the appropriate tier of an application's archi‑ tecture is crucial to preventing unauthorized actions, especially in dynamic environments. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 8.3.1 | 1 | Verify that the application enforces authorization rules at a trusted service layer and doesn't rely on controls that an untrusted consumer could manipulate, such as client‑side JavaScript. | Compliant | Presenter-level authorization checks. ACL resources mapped to presenter actions. | — | +| 8.3.2 | 3 | Verify that changes to values on which authorization decisions are made are applied immediately. Where changes cannot be applied immediately, (such as when relying on data in self‑contained tokens), there must be mitigating controls to alert when a consumer performs an action when they are no longer authorized to do so and revert the change. Note that this alternative would not mitigate information leakage. | | | | +| 8.3.3 | 3 | Verify that access to an object is based on the originating subject's (e.g. consumer's) permissions, not on the permissions of any intermediary or service acting on their behalf. For example, if a consumer calls a web service using a self‑contained token for authentication, and the service then requests data from a different service, the second service will use the consumer's token, rather than a machine‑to‑machine token from the first service, to make permission decisions. | | | | + +## V8.4 Other Authorization Considerations + +Additional considerations for authorization, particularly for administrative interfaces and multi‑ tenant environments, help prevent unauthorized access. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 8.4.1 | 2 | Verify that multi‑tenant applications use cross‑tenant controls to ensure consumer operations will never affect tenants with which they do not have permissions to interact. | Compliant | Multi-tenant data isolation via Doctrine security filters. Queries automatically scoped to current Account. Cross-tenant access prevented at ORM level. | — | +| 8.4.2 | 3 | Verify that access to administrative interfaces incorporates multiple layers of security, including continuous consumer identity verification, device security posture assessment, and contextual risk analysis, ensuring that network location or trusted endpoints are not the sole factors for authorization even though they may reduce the likelihood of unauthorized access. | | | | + +--- + +**Total requirements in this chapter: 13** +- Level 1: 4 +- Level 2: 3 +- Level 3: 6 diff --git a/ASVS/V09-Self-contained-Tokens.md b/ASVS/V09-Self-contained-Tokens.md new file mode 100644 index 0000000..6436363 --- /dev/null +++ b/ASVS/V09-Self-contained-Tokens.md @@ -0,0 +1,31 @@ +# V9 Self‑contained Tokens + +OWASP Application Security Verification Standard 5.0.0 + +## V9.1 Token source and integrity + +This section includes requirements to ensure that the token has been produced by a trusted party and has not been tampered with. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 9.1.1 | 1 | Verify that self‑contained tokens are validated using their digital signature or MAC to protect against tampering before accepting the token's contents. | Out of scope | Application uses reference tokens (DoctrineAuthenticator), not self-contained tokens like JWT/SAML. | — | +| 9.1.2 | 1 | Verify that only algorithms on an allowlist can be used to create and verify self‑contained tokens, for a given context. The allowlist must include the permitted algorithms, ideally only either symmetric or asymmetric algorithms, and must not include the 'None'algorithm. If both symmetric and asymmetric must be supported, additional controls will be needed to prevent key confusion. | Out of scope | Application uses reference tokens (DoctrineAuthenticator), not self-contained tokens like JWT/SAML. | — | +| 9.1.3 | 1 | Verify that key material that is used to validate self‑contained tokens is from trusted pre‑configured sources for the token issuer, preventing attackers from specifying untrusted sources and keys. For JWTs and other JWS structures, headers such as 'jku', 'x5u', and 'jwk'must be validated against an allowlist of trusted sources. | Out of scope | Application uses reference tokens (DoctrineAuthenticator), not self-contained tokens like JWT/SAML. | — | + +## V9.2 Token content + +Before making security decisions based on the content of a self‑contained token, it is necessary to validate that the token has been presented within its validity period and that it is intended for use by the receiving service and for the purpose for which it was presented. This helps avoid insecure cross‑usage between different services or with different token types from the same issuer. Specific requirements for OAuth and OIDC are covered in the dedicated chapter. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 9.2.1 | 1 | Verify that, if a validity time span is present in the token data, the token and its content are accepted only if the verification time is within this validity time span. For example, for JWTs, the claims 'nbf'and 'exp'must be verified. | Out of scope | Application uses reference tokens (DoctrineAuthenticator), not self-contained tokens like JWT/SAML. | — | +| 9.2.2 | 2 | Verify that the service receiving a token validates the token to be the correct type and is meant for the intended purpose before accepting the token's contents. For example, only access tokens can be accepted for authorization decisions and only ID Tokens can be used for proving user authentication. | Out of scope | Application uses reference tokens (DoctrineAuthenticator), not self-contained tokens like JWT/SAML. | — | +| 9.2.3 | 2 | Verify that the service only accepts tokens which are intended for use with that service (audience). For JWTs, this can be achieved by validating the 'aud' claim against an allowlist defined in the service. | Out of scope | Application uses reference tokens (DoctrineAuthenticator), not self-contained tokens like JWT/SAML. | — | +| 9.2.4 | 2 | Verify that, if a token issuer uses the same private key for issuing tokens to different audiences, the issued tokens contain an audience restriction that uniquely identifies the intended audiences. This will prevent a token from being reused with an unintended audience. If the audience identifier is dynamically provisioned, the token issuer must validate these audiences in order to make sure that they do not result in audience impersonation. | Out of scope | Application uses reference tokens (DoctrineAuthenticator), not self-contained tokens like JWT/SAML. | — | + +--- + +**Total requirements in this chapter: 7** +- Level 1: 4 +- Level 2: 3 +- Level 3: 0 diff --git a/ASVS/V10-OAuth-and-OIDC.md b/ASVS/V10-OAuth-and-OIDC.md new file mode 100644 index 0000000..8264ef1 --- /dev/null +++ b/ASVS/V10-OAuth-and-OIDC.md @@ -0,0 +1,95 @@ +# V10 OAuth and OIDC + +OWASP Application Security Verification Standard 5.0.0 + +## V10.1 Generic OAuth and OIDC Security + +This section covers generic architectural requirements that apply to all applications using OAuth or OIDC. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 10.1.1 | 2 | Verify that tokens are only sent to components that strictly need them. For example, when using a backend‑for‑frontend pattern for browser‑based JavaScript applications, access and refresh tokens shall only be accessible for the backend. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.1.2 | 2 | Verify that the client only accepts values from the authorization server (such as the authorization code or ID Token) if these values result from an authorization flow that was initiated by the same user agent session and transaction. This requires that client‑generated secrets, such as the proof key for code exchange (PKCE) 'code_verifier', 'state'or OIDC 'nonce', are not guessable, are specific to the transaction, and are securely bound to both the client and the user agent session in which the transaction was started. | Out of scope | Application does not use OAuth/OIDC. | — | + +## V10.2 OAuth Client + +These requirements detail the responsibilities for OAuth client applications. The client can be, for example, a web server backend (often acting as a Backend For Frontend, BFF), a backend service integration, or a frontend Single Page Application (SPA, aka browser‑based application). In general, backend clients are regarded as confidential clients and frontend clients are regarded as public clients. However, native applications running on the end‑user device can be regarded as confidential when using OAuth dynamic client registration. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 10.2.1 | 2 | Verify that, if the code flow is used, the OAuth client has protection against browser‑based request forgery attacks, commonly known as cross‑site request forgery (CSRF), which trigger token requests, either by using proof key for code exchange (PKCE) functionality or checking the 'state'parameter that was sent in the authorization request. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.2.2 | 2 | Verify that, if the OAuth client can interact with more than one authorization server, it has a defense against mix‑up attacks. For example, it could require that the authorization server return the 'iss'parameter value and validate it in the authorization response and the token response. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.2.3 | 3 | Verify that the OAuth client only requests the required scopes (or other authorization parameters) in requests to the authorization server. | | | | + +## V10.3 OAuth Resource Server + +In the context of ASVS and this chapter, the resource server is an API. To provide secure access, the resource server must: • Validate the access token, according to the token format and relevant protocol specifications, e.g., JWT‑validation or OAuth token introspection. • If valid, enforce authorization decisions based on the information from the access token and permissions which have been granted. For example, the resource server needs to verify that the client (acting on behalf of RO) is authorized to access the requested resource. Therefore, the requirements listed here are OAuth or OIDC specific and should be performed after token validation and before performing authorization based on information from the token. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 10.3.1 | 2 | Verify that the resource server only accepts access tokens that are intended for use with that service (audience). The audience may be included in a structured access token (such as the 'aud'claim in JWT), or it can be checked using the token introspection endpoint. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.3.2 | 2 | Verify that the resource server enforces authorization decisions based on claims from the access token that define delegated authorization. If claims such as 'sub', 'scope', and 'authorization_details'are present, they must be part of the decision. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.3.3 | 2 | Verify that if an access control decision requires identifying a unique user from an access token (JWT or related token introspection response), the resource server identifies the user from claims that cannot be reassigned to other users. Typically, it means using a combination of 'iss'and 'sub'claims. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.3.4 | 2 | Verify that, if the resource server requires specific authentication strength, methods, or recentness, it verifies that the presented access token satisfies these constraints. For example, if present, using the OIDC 'acr', 'amr'and 'auth_time'claims respectively. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.3.5 | 3 | Verify that the resource server prevents the use of stolen access tokens or replay of access tokens (from unauthorized parties) by requiring sender‑constrained access tokens, either Mutual TLS for OAuth 2 or OAuth 2 Demonstration of Proof of Possession (DPoP). | | | | + +## V10.4 OAuth Authorization Server + +These requirements detail the responsibilities for OAuth authorization servers, including OpenID Providers. For client authentication, the 'self_signed_tls_client_auth'method is allowed with the prerequisites required by section 2.2 of RFC 8705. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 10.4.1 | 1 | Verify that the authorization server validates redirect URIs based on a client‑specific allowlist of pre‑registered URIs using exact string comparison. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.2 | 1 | Verify that, if the authorization server returns the authorization code in the authorization response, it can be used only once for a token request. For the second valid request with an authorization code that has already been used to issue an access token, the authorization server must reject a token request and revoke any issued tokens related to the authorization code. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.3 | 1 | Verify that the authorization code is short‑lived. The maximum lifetime can be up to 10 minutes for L1 and L2 applications and up to 1 minute for L3 applications. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.4 | 1 | Verify that for a given client, the authorization server only allows the usage of grants that this client needs to use. Note that the grants 'token'(Implicit flow) and 'password'(Resource Owner Password Credentials flow) must no longer be used. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.5 | 1 | Verify that the authorization server mitigates refresh token replay attacks for public clients, preferably using sender‑constrained refresh tokens, i.e., Demonstrating Proof of Possession (DPoP) or Certificate‑Bound Access Tokens using mutual TLS (mTLS). For L1 and L2 applications, refresh token rotation may be used. If refresh token rotation is used, the authorization server must invalidate the refresh token after usage, and revoke all refresh tokens for that authorization if an already used and invalidated refresh token is provided. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.6 | 2 | Verify that, if the code grant is used, the authorization server mitigates authorization code interception attacks by requiring proof key for code exchange (PKCE). For authorization requests, the authorization server must require a valid 'code_challenge'value and must not accept a 'code_challenge_method'value of 'plain'. For a token request, it must require validation of the 'code_verifier'parameter. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.7 | 2 | Verify that if the authorization server supports unauthenticated dynamic client registration, it mitigates the risk of malicious client applications. It must validate client metadata such as any registered URIs, ensure the user's consent, and warn the user before processing an authorization request with an untrusted client application. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.8 | 2 | Verify that refresh tokens have an absolute expiration, including if sliding refresh token expiration is applied. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.9 | 2 | Verify that refresh tokens and reference access tokens can be revoked by an authorized user using the authorization server user interface, to mitigate the risk of malicious clients or stolen tokens. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.10 | 2 | Verify that confidential client is authenticated for client‑to‑authorized server backchannel requests such as token requests, pushed authorization requests (PAR), and token revocation requests. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.11 | 2 | Verify that the authorization server configuration only assigns the required scopes to the OAuth client. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.4.12 | 3 | Verify that for a given client, the authorization server only allows the 'response_mode'value that this client needs to use. For example, by having the authorization server validate this value against the expected values or by using pushed authorization request (PAR) or JWT‑secured Authorization Request (JAR). | | | | +| 10.4.13 | 3 | Verify that grant type 'code'is always used together with pushed authorization requests (PAR). | | | | +| 10.4.14 | 3 | Verify that the authorization server issues only sender‑constrained (Proof‑of‑Possession) access tokens, either with certificate‑bound access tokens using mutual TLS (mTLS) or DPoP‑bound access tokens (Demonstration of Proof of Possession). | | | | +| 10.4.15 | 3 | Verify that, for a server‑side client (which is not executed on the end‑user device), the authorization server ensures that the 'authorization_details' parameter value is from the client backend and that the user has not tampered with it. For example, by requiring the usage of pushed authorization request (PAR) or JWT‑secured Authorization Request (JAR). | | | | +| 10.4.16 | 3 | Verify that the client is confidential and the authorization server requires the use of strong client authentication methods (based on public‑key cryptography and resistant to replay attacks), such as mutual TLS ( 'tls_client_auth', 'self_signed_tls_client_auth') or private key JWT ( 'private_key_jwt'). | | | | + +## V10.5 OIDC Client + +As the OIDC relying party acts as an OAuth client, the requirements from the section "OAuth Client" apply as well. Note that the "Authentication with an Identity Provider"section in the "Authentication"chapter also contains relevant general requirements. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 10.5.1 | 2 | Verify that the client (as the relying party) mitigates ID Token replay attacks. For example, by ensuring that the 'nonce'claim in the ID Token matches the 'nonce'value sent in the authentication request to the OpenID Provider (in OAuth2 refereed to as the authorization request sent to the authorization server). | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.5.2 | 2 | Verify that the client uniquely identifies the user from ID Token claims, usually the 'sub'claim, which cannot be reassigned to other users (for the scope of an identity provider). | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.5.3 | 2 | Verify that the client rejects attempts by a malicious authorization server to impersonate another authorization server through authorization server metadata. The client must reject authorization server metadata if the issuer URL in the authorization server metadata does not exactly match the pre‑configured issuer URL expected by the client. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.5.4 | 2 | Verify that the client validates that the ID Token is intended to be used for that client (audience) by checking that the 'aud'claim from the token is equal to the 'client_id'value for the client. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.5.5 | 2 | Verify that, when using OIDC back‑channel logout, the relying party mitigates denial of service through forced logout and cross‑JWT confusion in the logout flow. The client must verify that the logout token is correctly typed with a value of 'logout+jwt', contains the 'event'claim with the correct member name, and does not contain a 'nonce'claim. Note that it is also recommended to have a short expiration (e.g., 2 minutes). | Out of scope | Application does not use OAuth/OIDC. | — | + +## V10.6 OpenID Provider + +As OpenID Providers act as OAuth authorization servers, the requirements from the section "OAuth Authorization Server"apply as well. Note that if using the ID Token flow (not the code flow), no access tokens are issued, and many of the requirements for OAuth AS are not applicable. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 10.6.1 | 2 | Verify that the OpenID Provider only allows values 'code', 'ciba', 'id_token', or 'id_token code'for response mode. Note that 'code'is preferred over 'id_token code'(the OIDC Hybrid flow), and 'token'(any Implicit flow) must not be used. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.6.2 | 2 | Verify that the OpenID Provider mitigates denial of service through forced logout. By obtaining explicit confirmation from the end‑user or, if present, validating parameters in the logout request (initiated by the relying party), such as the 'id_token_hint'. | Out of scope | Application does not use OAuth/OIDC. | — | + +## V10.7 Consent Management + +These requirements cover the verification of the user's consent by the authorization server. With‑ out proper user consent verification, a malicious actor may obtain permissions on the user's behalf through spoofing or social‑engineering. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 10.7.1 | 2 | Verify that the authorization server ensures that the user consents to each authorization request. If the identity of the client cannot be assured, the authorization server must always explicitly prompt the user for consent. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.7.2 | 2 | Verify that when the authorization server prompts for user consent, it presents sufficient and clear information about what is being consented to. When applicable, this should include the nature of the requested authorizations (typically based on scope, resource server, Rich Authorization Requests (RAR) authorization details), the identity of the authorized application, and the lifetime of these authorizations. | Out of scope | Application does not use OAuth/OIDC. | — | +| 10.7.3 | 2 | Verify that the user can review, modify, and revoke consents which the user has granted through the authorization server. | Out of scope | Application does not use OAuth/OIDC. | — | + +--- + +**Total requirements in this chapter: 36** +- Level 1: 5 +- Level 2: 24 +- Level 3: 7 diff --git a/ASVS/V11-Cryptography.md b/ASVS/V11-Cryptography.md new file mode 100644 index 0000000..60f75a9 --- /dev/null +++ b/ASVS/V11-Cryptography.md @@ -0,0 +1,83 @@ +# V11 Cryptography + +OWASP Application Security Verification Standard 5.0.0 + +## V11.1 Cryptographic Inventory and Documentation + +Applications need to be designed with strong cryptographic architecture to protect data assets ac‑ cording to their classification. Encrypting everything is wasteful; not encrypting anything is legally negligent. A balance must be struck, usually during architectural or high‑level design, design sprints, or architectural spikes. Designing cryptography "on the fly"or retrofitting it will inevitably cost much more to implement securely than simply building it in from the start. It is important to ensure that all cryptographic assets are regularly discovered, inventoried, and as‑ sessed. Please see the appendix for more information on how this can be done. The need to future‑proof cryptographic systems against the eventual rise of quantum computing is also critical. Post‑Quantum Cryptography (PQC) refers to cryptographic algorithms designed to + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 11.1.1 | 2 | Verify that there is a documented policy for management of cryptographic keys and a cryptographic key lifecycle that follows a key management standard such as NIST SP 800‑57. This should include ensuring that keys are not overshared (for example, with more than two entities for shared secrets and more than one entity for private keys). | Partial | No formal documented policy for cryptographic key management. Keys managed implicitly by framework and infrastructure. | Per-project: document crypto key management: bcrypt cost factor, session token entropy (`Random::generate(32)`), TLS cert rotation schedule. | +| 11.1.2 | 2 | Verify that a cryptographic inventory is performed, maintained, regularly updated, and includes all cryptographic keys, algorithms, and certificates used by the application. It must also document where keys can and cannot be used in the system, and the types of data that can and cannot be protected using the keys. | Partial | No cryptographic inventory maintained. Crypto usage: bcrypt (passwords), SHA-256 (session tokens), TLS (transport). | Per-project: document crypto inventory: `password_hash(PASSWORD_DEFAULT)` (bcrypt), `hash('sha256')` (session tokens), TLS (transport). | +| 11.1.3 | 3 | Verify that cryptographic discovery mechanisms are employed to identify all instances of cryptography in the system, including encryption, hashing, and signing operations. | | | | +| 11.1.4 | 3 | Verify that a cryptographic inventory is maintained. This must include a documented plan that outlines the migration path to new cryptographic standards, such as post‑quantum cryptography, in order to react to future threats. | | | | + +## V11.2 Secure Cryptography Implementation + +This section defines the requirements for the selection, implementation, and ongoing management of core cryptographic algorithms for an application. The objective is to ensure that only robust, industry‑accepted cryptographic primitives are deployed, in alignment with current standards (e.g., NIST, ISO/IEC) and best practices. Organizations must ensure that each cryptographic component is selected based on peer‑reviewed evidence and practical security testing. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 11.2.1 | 2 | Verify that industry‑validated implementations (including libraries and hardware‑accelerated implementations) are used for cryptographic operations. | Compliant | Using PHP built-in password_hash (bcrypt), hash('sha256') for tokens, and OpenSSL for TLS. All industry-validated implementations. | — | +| 11.2.2 | 2 | Verify that the application is designed with crypto agility such that random number, authenticated encryption, MAC, or hashing algorithms, key lengths, rounds, ciphers and modes can be reconfigured, upgraded, or swapped at any time, to protect against cryptographic breaks. Similarly, it must also be possible to replace keys and passwords and re‑encrypt data. This will allow for seamless upgrades to post‑quantum cryptography (PQC), once high‑assurance implementations of approved PQC schemes or standards are widely available. | Partial | No explicit crypto agility design. Changing algorithms would require code changes. | Pending fancyadmin: make hash algorithms configurable. Currently hardcoded: `PASSWORD_DEFAULT` in `IdentityTrait::setPassword()`, `sha256` in `DoctrineAuthenticator`. | +| 11.2.3 | 2 | Verify that all cryptographic primitives utilize a minimum of 128‑bits of security based on the algorithm, key size, and configuration. For example, a 256‑bit ECC key provides roughly 128 bits of security where RSA requires a 3072‑bit key to achieve 128 bits of security. | Compliant | bcrypt (128-bit security), SHA-256 (256-bit), TLS 1.2+ (128-bit minimum). All meet 112-bit minimum. | — | +| 11.2.4 | 3 | Verify that all cryptographic operations are constant‑time, with no 'short‑circuit'operations in comparisons, calculations, or returns, to avoid leaking information. | | | | +| 11.2.5 | 3 | Verify that all cryptographic modules fail securely, and errors are handled in a way that does not enable vulnerabilities, such as Padding Oracle attacks. | | | | + +## V11.3 Encryption Algorithms + +Authenticated encryption algorithms built on AES and CHACHA20 form the backbone of modern cryptographic practice. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 11.3.1 | 1 | Verify that insecure block modes (e.g., ECB) and weak padding schemes (e.g., PKCS#1 v1.5) are not used. | Out of scope | Application does not use block cipher modes directly. Encryption handled by TLS at transport layer. | — | +| 11.3.2 | 1 | Verify that only approved ciphers and modes such as AES with GCM are used. | Out of scope | Application does not perform application-level encryption. TLS cipher suite configured at nginx level. | — | +| 11.3.3 | 2 | Verify that encrypted data is protected against unauthorized modification preferably by using an approved authenticated encryption method or by combining an approved encryption method with an approved MAC algorithm. | Out of scope | No application-level encryption of stored data. Sensitive data protected by access control, not encryption at rest. | — | +| 11.3.4 | 3 | Verify that nonces, initialization vectors, and other single‑use numbers are not used for more than one encryption key and data‑element pair. The method of generation must be appropriate for the algorithm being used. | | | | +| 11.3.5 | 3 | Verify that any combination of an encryption algorithm and a MAC algorithm is operating in encrypt‑then‑MAC mode. | | | | + +## V11.4 Hashing and Hash‑based Functions + +Cryptographic hashes are used in a wide variety of cryptographic protocols, such as digital signatures, HMAC, key derivation functions (KDF), random bit generation, and password storage. The security of the cryptographic system is only as strong as the underlying hash functions used. This section outlines the requirements for using secure hash functions in cryptographic operations. For password storage, as well as the cryptography appendix, the OWASP Password Storage Cheat Sheet will also provide useful context and guidance. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 11.4.1 | 1 | Verify that only approved hash functions are used for general cryptographic use cases, including digital signatures, HMAC, KDF, and random bit generation. Disallowed hash functions, such as MD5, must not be used for any cryptographic purpose. | Compliant | Passwords hashed using bcrypt via PHP password_hash(). Nette Security default. | — | +| 11.4.2 | 2 | Verify that passwords are stored using an approved, computationally intensive, key derivation function (also known as a "password hashing function"), with parameter settings configured based on current guidance. The settings should balance security and performance to make brute‑force attacks sufficiently challenging for the required level of security. | Compliant | Passwords stored using bcrypt via PHP password_hash(PASSWORD_DEFAULT). Approved algorithm with automatic cost factor. | — | +| 11.4.3 | 2 | Verify that hash functions used in digital signatures, as part of data authentication or data integrity are collision resistant and have appropriate bit‑lengths. If collision resistance is required, the output length must be at least 256 bits. If only resistance to second pre‑image attacks is required, the output length must be at least 128 bits. | Compliant | SHA-256 used for session token hashing. Approved hash function for this use case. | — | +| 11.4.4 | 2 | Verify that the application uses approved key derivation functions with key stretching parameters when deriving secret keys from passwords. The parameters in use must balance security and performance to prevent brute‑force attacks from compromising the resulting cryptographic key. | Out of scope | No application-level key derivation. password_hash handles password KDF internally. | — | + +## V11.5 Random Values + +Cryptographically secure Pseudo‑random Number Generation (CSPRNG) is incredibly difficult to get right. Generally, good sources of entropy within a system will be quickly depleted if over‑used, but + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 11.5.1 | 2 | Verify that all random numbers and strings which are intended to be non‑guessable must be generated using a cryptographically secure pseudo‑random number generator (CSPRNG) and have at least 128 bits of entropy. Note that UUIDs do not respect this condition. | Compliant | Session tokens generated via Nette\Utils\Random::generate(32) which uses random_bytes() (CSPRNG). OTP tokens also use secure random generation. | — | +| 11.5.2 | 3 | Verify that the random number generation mechanism in use is designed to work securely, even under heavy demand. | | | | + +## V11.6 Public Key Cryptography + +Public Key Cryptography will be used where it is not possible or not desirable to share a secret key between multiple parties. As part of this, there exists a need for approved key exchange mechanisms, such as Diffie‑Hellman and Elliptic Curve Diffie‑Hellman (ECDH) to ensure that the cryptosystem remains secure against modern threats. The "Secure Communication"chapter provides requirements for TLS so the require‑ ments in this section are intended for situations where Public Key Cryptography is being used in use cases other than TLS. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 11.6.1 | 2 | Verify that only approved cryptographic algorithms and modes of operation are used for key generation and seeding, and digital signature generation and verification. Key generation algorithms must not generate insecure keys vulnerable to known attacks, for example, RSA keys which are vulnerable to Fermat factorization. | Out of scope | No application-level public key cryptography. TLS handled at infrastructure level. | — | +| 11.6.2 | 3 | Verify that approved cryptographic algorithms are used for key exchange (such as Diffie‑Hellman) with a focus on ensuring that key exchange mechanisms use secure parameters. This will prevent attacks on the key establishment process which could lead to adversary‑in‑the‑middle attacks or cryptographic breaks. | | | | + +## V11.7 In‑Use Data Cryptography + +Protecting data while it is being processed is paramount. Techniques such as full memory encryp‑ tion, encryption of data in transit, and ensuring data is encrypted as quickly as possible after use is recommended. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 11.7.1 | 3 | Verify that full memory encryption is in use that protects sensitive data while it is in use, preventing access by unauthorized users or processes. | | | | +| 11.7.2 | 3 | Verify that data minimization ensures the minimal amount of data is exposed during processing, and ensure that data is encrypted immediately after use or as soon as feasible. | | | | + +--- + +**Total requirements in this chapter: 24** +- Level 1: 3 +- Level 2: 11 +- Level 3: 10 diff --git a/ASVS/V12-Secure-Communication.md b/ASVS/V12-Secure-Communication.md new file mode 100644 index 0000000..7c88f29 --- /dev/null +++ b/ASVS/V12-Secure-Communication.md @@ -0,0 +1,43 @@ +# V12 Secure Communication + +OWASP Application Security Verification Standard 5.0.0 + +## V12.1 General TLS Security Guidance + +This section provides initial guidance on how to secure TLS communications. Up‑to‑date tools should be used to review TLS configuration on an ongoing basis. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 12.1.1 | 1 | Verify that only the latest recommended versions of the TLS protocol are enabled, such as TLS 1.2 and TLS 1.3. The latest version of the TLS protocol must be the preferred option. | Compliant | HTTPS enforced via nginx proxy. HSTS configured at proxy level. | — | +| 12.1.2 | 2 | Verify that only recommended cipher suites are enabled, with the strongest cipher suites set as preferred. L3 applications must only support cipher suites which provide forward secrecy. | Compliant | Cipher suite configuration managed at nginx reverse proxy level. Only modern cipher suites enabled. | — | +| 12.1.3 | 2 | Verify that the application validates that mTLS client certificates are trusted before using the certificate identity for authentication or authorization. | Out of scope | Application does not use mTLS client certificates. | — | +| 12.1.4 | 3 | Verify that proper certification revocation, such as Online Certificate Status Protocol (OCSP) Stapling, is enabled and configured. | | | | +| 12.1.5 | 3 | Verify that Encrypted Client Hello (ECH) is enabled in the application's TLS settings to prevent exposure of sensitive metadata, such as the Server Name Indication (SNI), during TLS handshake processes. | | | | + +## V12.2 HTTPS Communication with External Facing Services + +Ensure all HTTP traffic to external‑facing services which the application exposes is sent encrypted, with publicly trusted certificates. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 12.2.1 | 1 | Verify that TLS is used for all connectivity between a client and external facing, HTTP‑based services, and does not fall back to insecure or unencrypted communications. | Compliant | All external-facing communication over HTTPS. Enforced at nginx level. | — | +| 12.2.2 | 1 | Verify that external facing services use publicly trusted TLS certificates. | Compliant | Publicly trusted TLS certificates from Let's Encrypt / CA. Managed at infrastructure level. | — | + +## V12.3 General Service to Service Communication Security + +Server communications (both internal and external) involve more than just HTTP. Connections to and from other systems must also be secure, ideally using TLS. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 12.3.1 | 2 | Verify that an encrypted protocol such as TLS is used for all inbound and outbound connections to and from the application, including monitoring systems, management tools, remote access and SSH, middleware, databases, mainframes, partner systems, or external APIs. The server must not fall back to insecure or unencrypted protocols. | Compliant | All backend communication uses TLS-encrypted connections. Database accessed via internal network. | — | +| 12.3.2 | 2 | Verify that TLS clients validate certificates received before communicating with a TLS server. | Compliant | PHP cURL and HTTP clients validate TLS certificates by default. Certificate verification not disabled. | — | +| 12.3.3 | 2 | Verify that TLS or another appropriate transport encryption mechanism used for all connectivity between internal, HTTP‑based services within the application, and does not fall back to insecure or unencrypted communications. | Compliant | Database connections use TLS or are on localhost/internal network. No unencrypted external connections. | — | +| 12.3.4 | 2 | Verify that TLS connections between internal services use trusted certificates. Where internally generated or self‑signed certificates are used, the consuming service must be configured to only trust specific internal CAs and specific self‑signed certificates. | Partial | Internal service communication uses TLS where available. Some internal connections may use unencrypted localhost. | Per-project: set `sslmode=require` in Doctrine DBAL DSN for database. Verify all internal service connections use TLS. | +| 12.3.5 | 3 | Verify that services communicating internally within a system (intra‑service communications) use strong authentication to ensure that each endpoint is verified. Strong authentication methods, such as TLS client authentication, must be employed to ensure identity, using public‑key infrastructure and mechanisms that are resistant to replay attacks. For microservice architectures, consider using a service mesh to simplify certificate management and enhance security. | | | | + +--- + +**Total requirements in this chapter: 12** +- Level 1: 3 +- Level 2: 6 +- Level 3: 3 diff --git a/ASVS/V13-Configuration.md b/ASVS/V13-Configuration.md new file mode 100644 index 0000000..0794a1c --- /dev/null +++ b/ASVS/V13-Configuration.md @@ -0,0 +1,59 @@ +# V13 Configuration + +OWASP Application Security Verification Standard 5.0.0 + +## V13.1 Configuration Documentation + +This section outlines documentation requirements for how the application communicates with in‑ ternal and external services, as well as techniques to prevent loss of availability due to service inac‑ cessibility. It also addresses documentation related to secrets. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 13.1.1 | 2 | Verify that all communication needs for the application are documented. This must include external services which the application relies upon and cases where an end user might be able to provide an external location to which the application will then connect. | Partial | No formal documentation of all communication needs. HTTPS enforced for all external communication. | Per-project: document external dependencies (mail API, HaveIBeenPwned, database) with endpoints and auth methods. | +| 13.1.2 | 3 | Verify that for each service the application uses, the documentation defines the maximum number of concurrent connections (e.g., connection pool limits) and how the application behaves when that limit is reached, including any fallback or recovery mechanisms, to prevent denial of service conditions. | | | | +| 13.1.3 | 3 | Verify that the application documentation defines resource‑management strategies for every external system or service it uses (e.g., databases, file handles, threads, HTTP connections). This should include resource‑release procedures, timeout settings, failure handling, and where retry logic is implemented, specifying retry limits, delays, and back‑off algorithms. For synchronous HTTP request–response operations it should mandate short timeouts and either disable retries or strictly limit retries to prevent cascading delays and resource exhaustion. | | | | +| 13.1.4 | 3 | Verify that the application's documentation defines the secrets that are critical for the security of the application and a schedule for rotating them, based on the organization's threat model and business requirements. | | | | + +## V13.2 Backend Communication Configuration + +Applications interact with multiple services, including APIs, databases, or other components. These may be considered internal to the application but not included in the application's standard access control mechanisms, or they may be entirely external. In either case, it is necessary to configure the application to interact securely with these components and, if required, protect that configuration. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 13.2.1 | 2 | Verify that communications between backend application components that don't support the application's standard user session mechanism, including APIs, middleware, and data layers, are authenticated. Authentication must use individual service accounts, short‑term tokens, or certificate‑based authentication and not unchanging credentials such as passwords, API keys, or shared accounts with privileged access. | Compliant | Backend components communicate via authenticated database connections and internal HTTP APIs. | — | +| 13.2.2 | 2 | Verify that communications between backend application components, including local or operating system services, APIs, middleware, and data layers, are performed with accounts assigned the least necessary privileges. | Compliant | All backend communication uses encrypted connections (TLS or internal network). | — | +| 13.2.3 | 2 | Verify that if a credential has to be used for service authentication, the credential being used by the consumer is not a default credential (e.g., root/root or admin/admin). | Compliant | Service authentication uses environment variables (%env.BROKER_PASSWORD%, %env.MAILAPI_KEY%). Not hardcoded. | — | +| 13.2.4 | 2 | Verify that an allowlist is used to define the external resources or systems with which the application is permitted to communicate (e.g., for outbound requests, data loads, or file access). This allowlist can be implemented at the application layer, web server, firewall, or a combination of different layers. | Partial | No explicit allowlist of external resources. External connections limited to mail API and specific third-party services. | Per-project: define external resource allowlist (API endpoints, CDN). Optionally enforce via CSP `connect-src` in `common.neon`. | +| 13.2.5 | 2 | Verify that the web or application server is configured with an allowlist of resources or systems to which the server can send requests or load data or files from. | Compliant | Nette framework restricts allowed HTTP methods. Nginx configured with appropriate limits. | — | +| 13.2.6 | 3 | Verify that where the application connects to separate services, it follows the documented configuration for each connection, such as maximum parallel connections, behavior when maximum allowed connections is reached, connection timeouts, and retry strategies. | | | | + +## V13.3 Secret Management + +Secret management is an essential configuration task to ensure the protection of data used in the application. Specific requirements for cryptography can be found in the "Cryptography"chapter, but this section focuses on the management and handling aspects of secrets. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 13.3.1 | 2 | Verify that a secrets management solution, such as a key vault, is used to securely create, store, control access to, and destroy backend secrets. These could include passwords, key material, integrations with databases and third‑party systems, keys and seeds for time‑based tokens, other internal secrets, and API keys. Secrets must not be included in application source code or included in build artifacts. For an L3 application, this must involve a hardware‑backed solution such as an HSM. | Compliant | Secrets stored in environment variables (%env.BROKER_PASSWORD%, %env.MAILAPI_KEY%, etc.). Not in source code. | — | +| 13.3.2 | 2 | Verify that access to secret assets adheres to the principle of least privilege. | Compliant | Secrets accessible only to application runtime. Environment variables not exposed to frontend. Nette DI container restricts access. | — | +| 13.3.3 | 3 | Verify that all cryptographic operations are performed using an isolated security module (such as a vault or hardware security module) to securely manage and protect key material from exposure outside of the security module. | | | | +| 13.3.4 | 3 | Verify that secrets are configured to expire and be rotated based on the application's documentation. | | | | + +## V13.4 Unintended Information Leakage + +Production configurations should be hardened to avoid disclosing unnecessary data. Many of these issues are rarely rated as significant risks but are often chained with other vulnerabilities. If these issues are not present by default, it raises the bar for attacking an application. For example, hiding the version of server‑side components does not eliminate the need to patch all components, and disabling folder listing does not remove the need to use authorization controls or keep files away from the public folder, but it raises the bar. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 13.4.1 | 1 | Verify that the application is deployed either without any source control metadata, including the .git or .svn folders, or in a way that these folders are inaccessible both externally and to the application itself. | Compliant | Debug mode disabled in production (Tracy debugger). Error details not exposed to users. | — | +| 13.4.2 | 2 | Verify that debug modes are disabled for all components in production environments to prevent exposure of debugging features and information leakage. | Compliant | Nette framework default error handling. Stack traces only in debug mode. | — | +| 13.4.3 | 2 | Verify that web servers do not expose directory listings to clients unless explicitly intended. | Compliant | Nginx configured to deny directory listings. No autoindex enabled. | — | +| 13.4.4 | 2 | Verify that using the HTTP TRACE method is not supported in production environments, to avoid potential information leakage. | Compliant | HTTP TRACE method disabled at nginx level. | — | +| 13.4.5 | 2 | Verify that documentation (such as for internal APIs) and monitoring endpoints are not exposed unless explicitly intended. | Partial | No public API documentation or monitoring dashboards exposed. Internal docs should be access-controlled. | Per-project: verify Tracy debugger, Adminer, and monitoring endpoints are not publicly accessible in production. | +| 13.4.6 | 3 | Verify that the application does not expose detailed version information of backend components. | | | | +| 13.4.7 | 3 | Verify that the web tier is configured to only serve files with specific file extensions to prevent unintentional information, configuration, and source code leakage. | | | | + +--- + +**Total requirements in this chapter: 21** +- Level 1: 1 +- Level 2: 12 +- Level 3: 8 diff --git a/ASVS/V14-Data-Protection.md b/ASVS/V14-Data-Protection.md new file mode 100644 index 0000000..8314040 --- /dev/null +++ b/ASVS/V14-Data-Protection.md @@ -0,0 +1,44 @@ +# V14 Data Protection + +OWASP Application Security Verification Standard 5.0.0 + +## V14.1 Data Protection Documentation + +A key prerequisite for being able to protect data is to categorize what data should be considered sensitive. There are likely to be several different levels of sensitivity, and for each level, the controls required to protect data at that level will be different. There are various privacy regulations and laws that affect how applications must approach the stor‑ age, use, and transmission of sensitive personal information. This section no longer tries to duplicate + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 14.1.1 | 2 | Verify that all sensitive data created and processed by the application has been identified and classified into protection levels. This includes data that is only encoded and therefore easily decoded, such as Base64 strings or the plaintext payload inside a JWT. Protection levels need to take into account any data protection and privacy regulations and standards which the application is required to comply with. | Partial | No formal data classification document. Sensitive data identified implicitly (passwords, tokens, personal data). | Per-project: classify data: passwords (critical — bcrypt), session tokens (critical — SHA-256+DB), PII/email (high — ACL), business data (medium). | +| 14.1.2 | 2 | Verify that all sensitive data protection levels have a documented set of protection requirements. This must include (but not be limited to) requirements related to general encryption, integrity verification, retention, how the data is to be logged, access controls around sensitive data in logs, database‑level encryption, privacy and privacy‑enhancing technologies to be used, and other confidentiality requirements. | Partial | No documented protection levels per data classification. Protection applied based on development practices. | Per-project: for each classification level, document: encryption method, retention period, logging rules, access controls. | + +## V14.2 General Data Protection + +This section contains various practical requirements related to the protection of data. Most are spe‑ cific to particular issues such as unintended data leakage, but there is also a general requirement to implement protection controls based on the protection level required for each data item. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 14.2.1 | 1 | Verify that sensitive data is only sent to the server in the HTTP message body or header fields, and that the URL and query string do not contain sensitive information, such as an API key or session token. | Compliant | HTTPS only. Sensitive data transmitted over TLS. | — | +| 14.2.2 | 2 | Verify that the application prevents sensitive data from being cached in server components, such as load balancers and application caches, or ensures that the data is securely purged after use. | Compliant | Latte templates do not expose sensitive data in HTML. Server-side rendering with access control. | — | +| 14.2.3 | 2 | Verify that defined sensitive data is not sent to untrusted parties (e.g., user trackers) to prevent unwanted collection of data outside of the application's control. | Compliant | Sensitive data not sent to untrusted third parties. Only necessary data sent to mail API. | — | +| 14.2.4 | 2 | Verify that controls around sensitive data related to encryption, integrity verification, retention, how the data is to be logged, access controls around sensitive data in logs, privacy and privacy‑enhancing technologies, are implemented as defined in the documentation for the specific data's protection level. | Partial | No formal verification of encryption controls per data classification. TLS protects data in transit. | Per-project: verify each data classification has matching technical controls (TLS, bcrypt, Doctrine security filters, log masking). | +| 14.2.5 | 3 | Verify that caching mechanisms are configured to only cache responses which have the expected content type for that resource and do not contain sensitive, dynamic content. The web server should return a 404 or 302 response when a non‑existent file is accessed rather than returning a different, valid file. This should prevent Web Cache Deception attacks. | | | | +| 14.2.6 | 3 | Verify that the application only returns the minimum required sensitive data for the application's functionality. For example, only returning some of the digits of a credit card number and not the full number. If the complete data is required, it should be masked in the user interface unless the user specifically views it. | | | | +| 14.2.7 | 3 | Verify that sensitive information is subject to data retention classification, ensuring that outdated or unnecessary data is deleted automatically, on a defined schedule, or as the situation requires. | | | | +| 14.2.8 | 3 | Verify that sensitive information is removed from the metadata of user‑submitted files unless storage is consented to by the user. | | | | + +## V14.3 Client‑side Data Protection + +This section contains requirements preventing data from leaking in specific ways at the client or user agent side of an application. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 14.3.1 | 1 | Verify that authenticated data is cleared from client storage, such as the browser DOM, after the client or session is terminated. The 'Clear‑Site‑Data' HTTP response header field may be able to help with this but the client‑side should also be able to clear up if the server connection is not available when the session is terminated. | Compliant | Logout clears authentication cookie via CookieStorage::clearAuthentication(). Session invalidated server-side. | — | +| 14.3.2 | 2 | Verify that the application sets sufficient anti‑caching HTTP response header fields (i.e., Cache‑Control: no‑store) so that sensitive data is not cached in browsers. | Compliant | Cache-Control: no-store set globally via fancyadmin config/security.neon http: headers:. Prevents browser caching of sensitive responses. | — | +| 14.3.3 | 2 | Verify that data stored in browser storage (such as localStorage, sessionStorage, IndexedDB, or cookies) does not contain sensitive data, with the exception of session tokens. | Partial | No audit of client-side storage (localStorage, sessionStorage) for sensitive data. Minimal client-side storage used. | Per-project: audit `localStorage`/`sessionStorage`/`IndexedDB`. Ensure no PII or tokens stored beyond `__Host-userid` cookie. | + +--- + +**Total requirements in this chapter: 13** +- Level 1: 2 +- Level 2: 7 +- Level 3: 4 diff --git a/ASVS/V15-Secure-Coding-and-Architecture.md b/ASVS/V15-Secure-Coding-and-Architecture.md new file mode 100644 index 0000000..c636a95 --- /dev/null +++ b/ASVS/V15-Secure-Coding-and-Architecture.md @@ -0,0 +1,59 @@ +# V15 Secure Coding and Architecture + +OWASP Application Security Verification Standard 5.0.0 + +## V15.1 Secure Coding and Architecture Documentation + +Many requirements for establishing a secure and defensible architecture depend on clear documen‑ tation of decisions made regarding the implementation of specific security controls and the compo‑ nents used within the application. This section outlines the documentation requirements, including identifying components consid‑ ered to contain "dangerous functionality"or to be "risky components." + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 15.1.1 | 1 | Verify that application documentation defines risk based remediation time frames for 3rd party component versions with vulnerabilities and for updating libraries in general, to minimize the risk from these components. | Partial | No formal vulnerability remediation policy. Composer dependencies updated periodically. | Per-project: define vulnerability remediation SLA (e.g., critical: 48h, high: 7d, medium: 30d) in project security policy. | +| 15.1.2 | 2 | Verify that an inventory catalog, such as software bill of materials (SBOM), is maintained of all third‑party libraries in use, including verifying that components come from pre‑defined, trusted, and continually maintained repositories. | Partial | composer.lock serves as dependency inventory. No formal SBOM generated. | Run `composer require --dev cyclonedx/cyclonedx-php-composer` and add SBOM generation to CI/CD pipeline. | +| 15.1.3 | 2 | Verify that the application documentation identifies functionality which is time‑consuming or resource‑demanding. This must include how to prevent a loss of availability due to overusing this functionality and how to avoid a situation where building a response takes longer than the consumer's timeout. Potential defenses may include asynchronous processing, using queues, and limiting parallel processes per user and per application. | Partial | Sensitive data flows not formally documented. Known flows: authentication, password reset, personal data editing. | Per-project: document resource-intensive features (bulk imports, exports, large queries) and mitigations (queues, rate limits). | +| 15.1.4 | 3 | Verify that application documentation highlights third‑party libraries which are considered to be "risky components". | | | | +| 15.1.5 | 3 | Verify that application documentation highlights parts of the application where "dangerous functionality"is being used. | | | | + +## V15.2 Security Architecture and Dependencies + +This section includes requirements for handling risky, outdated, or insecure dependencies and com‑ ponents through dependency management. It also includes using architectural‑level techniques such as sandboxing, encapsulation, container‑ ization, and network isolation to reduce the impact of using "dangerous operations"or "risky compo‑ + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 15.2.1 | 1 | Verify that the application only contains components which have not breached the documented update and remediation time frames. | Compliant | All dependencies managed via Composer with known versions. No abandoned packages in use. | — | +| 15.2.2 | 2 | Verify that the application has implemented defenses against loss of availability due to functionality which is time‑consuming or resource‑demanding, based on the documented security decisions and strategies for this. | Partial | No automated dependency vulnerability scanning (e.g., composer audit). Should be added to CI/CD. | Add `composer audit` step to CI/CD pipeline. Fail build on known vulnerabilities. | +| 15.2.3 | 2 | Verify that the production environment only includes functionality that is required for the application to function, and does not expose extraneous functionality such as test code, sample snippets, and development functionality. | Compliant | Production environment only includes production dependencies. Dev dependencies excluded via composer install --no-dev. | — | +| 15.2.4 | 3 | Verify that third‑party components and all of their transitive dependencies are included from the expected repository, whether internally owned or an external source, and that there is no risk of a dependency confusion attack. | | | | +| 15.2.5 | 3 | Verify that the application implements additional protections around parts of the application which are documented as containing "dangerous functionality"or using third‑party libraries considered to be "risky components". This could include techniques such as sandboxing, encapsulation, containerization or network level isolation to delay and deter attackers who compromise one part of an application from pivoting elsewhere in the application. | | | | + +## V15.3 Defensive Coding + +This section covers vulnerability types, including type juggling, prototype pollution, and others, which result from using insecure coding patterns in a particular language. Some may not be relevant to all languages, whereas others will have language‑specific fixes or may relate to how a particular language or framework handles a feature such as HTTP parameters. It also considers the risk of not cryptographically validating application updates. It also considers the risks associated with using objects to represent data items and accepting and returning these via external APIs. In this case, the application must ensure that data fields that should not be writable are not modified by user input (mass assignment) and that the API is selective about what data fields get returned. Where field access depends on a user's permissions, this should be considered in the context of the field‑level access control requirement in the Authorization chapter. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 15.3.1 | 1 | Verify that the application only returns the required subset of fields from a data object. For example, it should not return an entire data object, as some individual fields should not be accessible to users. | Compliant | Doctrine queries return only requested fields. Presenters pass only needed data to templates. | — | +| 15.3.2 | 2 | Verify that where the application backend makes calls to external URLs, it is configured to not follow redirects unless it is intended functionality. | Compliant | External API calls (mail service) use validated, hardcoded endpoints. No SSRF vectors. | — | +| 15.3.3 | 2 | Verify that the application has countermeasures to protect against mass assignment attacks by limiting allowed fields per controller and action, e.g., it is not possible to insert or update a field value when it was not intended to be part of that action. | Partial | No explicit mass assignment protection beyond Nette form validation. Doctrine entities set properties individually. | Per-project: ensure entities are only updated via `$form->getValues()` fields. Never use `$httpRequest->getPost()` directly on entities. | +| 15.3.4 | 2 | Verify that all proxying and middleware components transfer the user's original IP address correctly using trusted data fields that cannot be manipulated by the end user, and the application and web server use this correct value for logging and security decisions such as rate limiting, taking into account that even the original IP address may not be reliable due to dynamic IPs, VPNs, or corporate firewalls. | Out of scope | No proxying or middleware components that transfer client info. | — | +| 15.3.5 | 2 | Verify that the application explicitly ensures that variables are of the correct type and performs strict equality and comparator operations. This is to avoid type juggling or type confusion vulnerabilities caused by the application code making an assumption about a variable type. | Compliant | PHP initializes variables explicitly. Nette framework uses strict types throughout. | — | +| 15.3.6 | 2 | Verify that JavaScript code is written in a way that prevents prototype pollution, for example, by using Set() or Map() instead of object literals. | Partial | Client-side JS uses standard patterns. No comprehensive prototype pollution audit. | Per-project: audit client-side JS. Use `Object.create(null)` or `Map`/`Set` instead of `{}` for user-influenced data. | +| 15.3.7 | 2 | Verify that the application has defenses against HTTP parameter pollution attacks, particularly if the application framework makes no distinction about the source of request parameters (query string, body parameters, cookies, or header fields). | Compliant | Nette framework handles HTTP parameter parsing. No duplicate parameter vulnerability. | — | + +## V15.4 Safe Concurrency + +Concurrency issues such as race conditions, time‑of‑check to time‑of‑use (TOCTOU) vulnerabilities, deadlocks, livelocks, thread starvation, and improper synchronization can lead to unpredictable be‑ havior and security risks. This section includes various techniques and strategies to help mitigate these risks. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 15.4.1 | 3 | Verify that shared objects in multi‑threaded code (such as caches, files, or in‑memory objects accessed by multiple threads) are accessed safely by using thread‑safe types and synchronization mechanisms like locks or semaphores to avoid race conditions and data corruption. | | | | +| 15.4.2 | 3 | Verify that checks on a resource's state, such as its existence or permissions, and the actions that depend on them are performed as a single atomic operation to prevent time‑of‑check to time‑of‑use (TOCTOU) race conditions. For example, checking if a file exists before opening it, or verifying a user's access before granting it. | | | | +| 15.4.3 | 3 | Verify that locks are used consistently to avoid threads getting stuck, whether by waiting on each other or retrying endlessly, and that locking logic stays within the code responsible for managing the resource to ensure locks cannot be inadvertently or maliciously modified by external classes or code. | | | | +| 15.4.4 | 3 | Verify that resource allocation policies prevent thread starvation by ensuring fair access to resources, such as by leveraging thread pools, allowing lower‑priority threads to proceed within a reasonable timeframe. | | | | + +--- + +**Total requirements in this chapter: 21** +- Level 1: 3 +- Level 2: 10 +- Level 3: 8 diff --git a/ASVS/V16-Security-Logging-and-Error-Handling.md b/ASVS/V16-Security-Logging-and-Error-Handling.md new file mode 100644 index 0000000..dc9cacb --- /dev/null +++ b/ASVS/V16-Security-Logging-and-Error-Handling.md @@ -0,0 +1,62 @@ +# V16 Security Logging and Error Handling + +OWASP Application Security Verification Standard 5.0.0 + +## V16.1 Security Logging Documentation + +This section ensures a clear and complete inventory of logging across the application stack. This is essential for effective security monitoring, incident response, and compliance. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 16.1.1 | 2 | Verify that an inventory exists documenting the logging performed at each layer of the application's technology stack, what events are being logged, log formats, where that logging is stored, how it is used, how access to it is controlled, and for how long logs are kept. | Partial | No formal logging inventory. Tracy logger handles errors. Application-level security logging not comprehensive. | Per-project: document logging inventory: Tracy error logs, `DoctrineLoggable` ChangeLog table, `LoginAttempt` table, `StorageEntity` sessions. | + +## V16.2 General Logging + +This section provides requirements to ensure that security logs are consistently structured and con‑ tain the expected metadata. The goal is to make logs machine‑readable and analyzable across dis‑ tributed systems and tools. Naturally, security events often involve sensitive data. If such data is logged without consideration, the logs themselves become classified and therefore subject to encryption requirements, stricter re‑ tention policies, and potential disclosure during audits. Therefore, it is critical to log only what is necessary and to treat log data with the same care as other sensitive assets. The requirements below establish foundational requirements for logging metadata, synchroniza‑ tion, format, and control. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 16.2.1 | 2 | Verify that each log entry includes necessary metadata (such as when, where, who, what) that would allow for a detailed investigation of the timeline when an event happens. | Partial | Tracy log entries include timestamp, file, line. Missing: user ID, IP address, request context in application logs. | Pending fancyadmin: enrich security logs with `$identity->getId()`, `$httpRequest->getRemoteAddress()`, and request context. | +| 16.2.2 | 2 | Verify that time sources for all logging components are synchronized, and that timestamps in security event metadata use UTC or include an explicit time zone offset. UTC is recommended to ensure consistency across distributed systems and to prevent confusion during daylight saving time transitions. | Compliant | Server time synchronized via NTP. Single timezone used consistently. | — | +| 16.2.3 | 2 | Verify that the application only stores or broadcasts logs to the files and services that are documented in the log inventory. | Compliant | Tracy logs stored on server filesystem. Nette does not broadcast logs to untrusted services. | — | +| 16.2.4 | 2 | Verify that logs can be read and correlated by the log processor that is in use, preferably by using a common logging format. | Partial | Tracy logs are text files. No structured logging format (JSON) for automated processing. | Per-project: consider Monolog with JSON formatter instead of Tracy file-based logs for machine-readable structured logging. | +| 16.2.5 | 2 | Verify that when logging sensitive data, the application enforces logging based on the data's protection level. For example, it may not be allowed to log certain data, such as credentials or payment details. Other data, such as session tokens, may only be logged by being hashed or masked, either in full or partially. | Partial | No explicit PII masking in logs. Passwords not logged, but user emails may appear in error context. | Per-project: register custom Tracy log processor to redact emails and PII from error context before writing to logs. | + +## V16.3 Security Events + +This section defines requirements for logging security‑relevant events within the application. Cap‑ turing these events is critical for detecting suspicious behavior, supporting investigations, and ful‑ filling compliance obligations. This section outlines the types of events that should be logged but does not attempt to provide ex‑ haustive detail. Each application has unique risk factors and operational context. Note that while ASVS includes logging of security events in scope, alerting and correlation (e.g., SIEM rules or monitoring infrastructure) are considered out of scope and are handled by operational and monitoring systems. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 16.3.1 | 2 | Verify that all authentication operations are logged, including successful and unsuccessful attempts. Additional metadata, such as the type of authentication or factors used, should also be collected. | Compliant | Failed login attempts logged via LoginAttempt entity (IP, timestamp). Successful logins logged via StorageEntity (session table) with createdAt, objectId, IP, userAgent, context. Password changes logged via DoctrineLoggable (ChangeLog table). | — | +| 16.3.2 | 2 | Verify that failed authorization attempts are logged. For L3, this must include logging all authorization decisions, including logging when sensitive data is accessed (without logging the sensitive data itself). | Partial | Authorization failures result in HTTP 403 via Nette ACL. Not explicitly logged as security events. | Pending fancyadmin: log `$user->isAllowed()` failures in `AuthPresenterTrait::checkRequirements()` as security events. | +| 16.3.3 | 2 | Verify that the application logs the security events that are defined in the documentation and also logs attempts to bypass the security controls, such as input validation, business logic, and anti‑automation. | Partial | No comprehensive security event logging. Fraud detection logged via onFraudDetection callback. Most security events not explicitly logged. | Pending fancyadmin: add security event logging for: rate limit triggers, session invalidation, password changes, CSRF failures. | +| 16.3.4 | 2 | Verify that the application logs unexpected errors and security control failures such as backend TLS failures. | Compliant | Tracy logger captures all uncaught exceptions and errors. Error details logged server-side, generic message shown to user. | — | + +## V16.4 Log Protection + +Logs are valuable forensic artifacts and must be protected. If logs can be easily modified or deleted, they lose their integrity and become unreliable for incident investigations or legal proceedings. Logs may expose internal application behavior or sensitive metadata, making them an attractive target for attackers. This section defines requirements to ensure that logs are protected from unauthorized access, tam‑ pering, and disclosure, and that they are safely transmitted and stored in secure, isolated systems. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 16.4.1 | 2 | Verify that all logging components appropriately encode data to prevent log injection. | Compliant | Tracy logger encodes output. Log injection not a risk with file-based logging. | — | +| 16.4.2 | 2 | Verify that logs are protected from unauthorized access and cannot be modified. | Compliant | Log files stored on server filesystem with restricted permissions. Not accessible via web. | — | +| 16.4.3 | 2 | Verify that logs are securely transmitted to a logically separate system for analysis, detection, alerting, and escalation. The aim is to ensure that if the application is breached, the logs are not compromised. | Partial | Logs stored locally on application server. No centralized logging (SIEM) or log forwarding configured. | Per-project: set up log forwarding (syslog/ELK/Monolog handler) to a system separate from the application server. | + +## V16.5 Error Handling + +This section defines requirements to ensure that applications fail gracefully and securely without disclosing sensitive internal details. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 16.5.1 | 2 | Verify that a generic message is returned to the consumer when an unexpected or security‑sensitive error occurs, ensuring no exposure of sensitive internal system data such as stack traces, queries, secret keys, and tokens. | Compliant | Nette framework catches exceptions globally. Tracy logger for error recording. No sensitive data in error responses. | — | +| 16.5.2 | 2 | Verify that the application continues to operate securely when external resource access fails, for example, by using patterns such as circuit breakers or graceful degradation. | Compliant | Tracy logger handles exceptions. No stack traces exposed in production. | — | +| 16.5.3 | 2 | Verify that the application fails gracefully and securely, including when an exception occurs, preventing fail‑open conditions such as processing a transaction despite errors resulting from validation logic. | Compliant | Tracy error handler catches all exceptions. Application fails gracefully with generic error page in production. No sensitive data in error responses. | — | +| 16.5.4 | 3 | Verify that a "last resort"error handler is defined which will catch all unhandled exceptions. This is both to avoid losing error details that must go to log files and to ensure that an error does not take down the entire application process, leading to a loss of availability. Note: Certain languages, (including Swift, Go, and through common design practice, many func‑ tional languages,) do not support exceptions or last‑resort event handlers. In this case, architects and developers should use a pattern, language, or framework‑friendly way to ensure that applica‑ tions can securely handle exceptional, unexpected, or security‑related events. | | | | + +--- + +**Total requirements in this chapter: 17** +- Level 1: 0 +- Level 2: 16 +- Level 3: 1 diff --git a/ASVS/V17-WebRTC.md b/ASVS/V17-WebRTC.md new file mode 100644 index 0000000..cf2f103 --- /dev/null +++ b/ASVS/V17-WebRTC.md @@ -0,0 +1,43 @@ +# V17 WebRTC + +OWASP Application Security Verification Standard 5.0.0 + +## V17.1 TURN Server + +This section defines security requirements for systems that operate their own TURN (Traversal Us‑ ing Relays around NAT) servers. TURN servers assist in relaying media in restrictive network envi‑ ronments but can pose risks if misconfigured. These controls focus on secure address filtering and protection against resource exhaustion. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 17.1.1 | 2 | Verify that the Traversal Using Relays around NAT (TURN) service only allows access to IP addresses that are not reserved for special purposes (e.g., internal networks, broadcast, loopback). Note that this applies to both IPv4 and IPv6 addresses. | Out of scope | Application does not use WebRTC. WebSocket connections are project-specific, out of fancyadmin scope. | — | +| 17.1.2 | 3 | Verify that the Traversal Using Relays around NAT (TURN) service is not susceptible to resource exhaustion when legitimate users attempt to open a large number of ports on the TURN server. | Out of scope | Application does not use WebRTC. | — | + +## V17.2 Media + +These requirements only apply to systems that host their own WebRTC media servers, such as Selec‑ tive Forwarding Units (SFUs), Multipoint Control Units (MCUs), recording servers, or gateway servers. Media servers handle and distribute media streams, making their security critical to protect commu‑ nication between peers. Safeguarding media streams is paramount in WebRTC applications to pre‑ vent eavesdropping, tampering, and denial‑of‑service attacks that could compromise user privacy and communication quality. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 17.2.1 | 2 | Verify that the key for the Datagram Transport Layer Security (DTLS) certificate is managed and protected based on the documented policy for management of cryptographic keys. | Out of scope | Application does not use WebRTC. | — | +| 17.2.2 | 2 | Verify that the media server is configured to use and support approved Datagram Transport Layer Security (DTLS) cipher suites and a secure protection profile for the DTLS Extension for establishing keys for the Secure Real‑time Transport Protocol (DTLS‑SRTP). | Out of scope | Application does not use WebRTC. | — | +| 17.2.3 | 2 | Verify that Secure Real‑time Transport Protocol (SRTP) authentication is checked at the media server to prevent Real‑time Transport Protocol (RTP) injection attacks from leading to either a Denial of Service condition or audio or video media insertion into media streams. | Out of scope | Application does not use WebRTC. | — | +| 17.2.4 | 2 | Verify that the media server is able to continue processing incoming media traffic when encountering malformed Secure Real‑time Transport Protocol (SRTP) packets. | Out of scope | Application does not use WebRTC. | — | +| 17.2.5 | 3 | Verify that the media server is able to continue processing incoming media traffic during a flood of Secure Real‑time Transport Protocol (SRTP) packets from legitimate users. | Out of scope | Application does not use WebRTC. | — | +| 17.2.6 | 3 | Verify that the media server is not susceptible to the "ClientHello"Race Condition vulnerability in Datagram Transport Layer Security (DTLS) by checking if the media server is publicly known to be vulnerable or by performing the race condition test. | Out of scope | Application does not use WebRTC. | — | +| 17.2.7 | 3 | Verify that any audio or video recording mechanisms associated with the media server are able to continue processing incoming media traffic during a flood of Secure Real‑time Transport Protocol (SRTP) packets from legitimate users. | Out of scope | Application does not use WebRTC. | — | +| 17.2.8 | 3 | Verify that the Datagram Transport Layer Security (DTLS) certificate is checked against the Session Description Protocol (SDP) fingerprint attribute, terminating the media stream if the check fails, to ensure the authenticity of the media stream. | Out of scope | Application does not use WebRTC. | — | + +## V17.3 Signaling + +This section defines requirements for systems that operate their own WebRTC signaling servers. Sig‑ naling coordinates peer‑to‑peer communication and must be resilient against attacks that could dis‑ rupt session establishment or control. To ensure secure signaling, systems must handle malformed inputs gracefully and remain available under load. + +| # | Level | Requirement | Status | How We Comply | What to Do | +|---|-------|-------------|--------|---------------|------------| +| 17.3.1 | 2 | Verify that the signaling server is able to continue processing legitimate incoming signaling messages during a flood attack. This should be achieved by implementing rate limiting at the signaling level. | Out of scope | Application does not use WebRTC. | — | +| 17.3.2 | 2 | Verify that the signaling server is able to continue processing legitimate signaling messages when encountering malformed signaling message that could cause a denial of service condition. This could include implementing input validation, safely handling integer overflows, preventing buffer overflows, and employing other robust error‑handling techniques. | Out of scope | Application does not use WebRTC. | — | + +--- + +**Total requirements in this chapter: 12** +- Level 1: 0 +- Level 2: 7 +- Level 3: 5 diff --git a/README.md b/README.md index 42f24ec..de2083b 100644 --- a/README.md +++ b/README.md @@ -1 +1,1229 @@ -# fancyadmin \ No newline at end of file +# Integrace adt/fancyadmin do Nette projektu + +Tento dokument popisuje krok za krokem, jak integrovat balíček `adt/fancyadmin` do nového Nette 3.x projektu. + +--- + +## Předpoklady + +Projekt musí mít nainstalováno: +- PHP >= 8.4 +- Nette 3.1+ +- Nettrine ORM (`nettrine/orm ^0.10`, `nettrine/dbal ^0.10`) +- Nettrine Migrations (`nettrine/migrations ^0.10`) +- `kdyby/autowired ^3.1` +- `contributte/console ^0.10` +- MySQL 8.0 + +--- + +## 1. Composer require + +```bash +composer require adt/fancyadmin:^1.0 +``` + +Fancyadmin automaticky stáhne tyto závislosti: +- `adt/doctrine-authenticator` — autentizace přes Doctrine +- `adt/doctrine-components` — BaseEntity, QueryObject, EntityManager +- `adt/doctrine-forms` — formuláře napojené na Doctrine entity +- `adt/nette-forms-components` — rozšířené formulářové prvky +- `adt/datagrid-components` — datagridy +- `adt/files` — správa souborů +- `adt/doctrine-loggable` — audit log +- `contributte/translation` — překlady +- `nette/forms`, `nette/security`, `nette/mail` +- `ublaboo/datagrid` + +Doplňkově doporučeno: +```bash +composer require adt/doctrine-components:^3.2 adt/query-object-data-source:^3.0 +``` + +--- + +## 2. BaseEntity + +Vytvořte abstraktní BaseEntity, od které budou dědit všechny entity: + +```php +// app/Model/Entities/Abstract/BaseEntity.php +accounts = new ArrayCollection(); + } +} +``` + +**AccountTrait poskytuje:** `name`, `parent` (self-ref), `accounts` (sub-accounts), timestamps + +### 3.3 Profile + +```php +// app/Model/Entities/Profile.php +value; + } +} +``` + +--- + +## 5. Query třídy + +Fancyadmin vyžaduje QueryObject pattern z `adt/doctrine-components`. Každý query objekt: +- dědí z BaseQuery (rozšiřuje `ADT\DoctrineComponents\QueryObject\QueryObject`) +- implementuje interface z fancyadmin +- používá odpovídající trait z fancyadmin + +### 5.1 BaseQuery + +```php +// app/Model/Queries/Abstract/BaseQuery.php + + * @template TEntity of object + */ +abstract class BaseQuery extends QueryObject implements OrByIdFilterInterface, \ADT\FancyAdmin\Model\Queries\Abstract\BaseQuery +{ + use BaseQueryTrait; +} +``` + +### 5.2 Konkrétní Query třídy + +Vzor je pro všechny stejný — implementovat interface, použít trait, přidat stub metody: + +```php +// app/Model/Queries/IdentityQuery.php + + */ +class IdentityQuery extends Abstract\BaseQuery + implements \ADT\FancyAdmin\Model\Queries\IdentityQuery, + \ADT\DoctrineAuthenticator\OTP\IdentityQuery +{ + use IdentityQueryTrait; + + protected function applySecurityFilter(): void {} + protected function applyAccountFilter(QueryBuilder $qb, Account $account): void {} + protected function setDefaultOrder(): void {} +} +``` + +Stejný vzor pro: +- **AccountQuery** — `use AccountQueryTrait; implements \ADT\FancyAdmin\Model\Queries\AccountQuery` +- **ProfileQuery** — `use ProfileQueryTrait; implements \ADT\FancyAdmin\Model\Queries\ProfileQuery` +- **AclRoleQuery** — `use AclRoleQueryTrait; implements \ADT\FancyAdmin\Model\Queries\AclRoleQuery` (+ `applyAccountFilter`) +- **ConfigurationQuery** — `use ConfigurationQueryTrait; implements \ADT\FancyAdmin\Model\Queries\ConfigurationQuery` +- **GridFilterQuery** — `use \ADT\Datagrid\Model\Queries\GridFilterQueryTrait; implements \ADT\Datagrid\Model\Queries\GridFilterQuery` + +### 5.3 DefaultFilters trait + +```php +// app/Model/Queries/Filters/DefaultFilters.php +getRouteList(); + + // Web module routes + $webModule = new RouteList('Web'); + $webModule->addRoute('/[/]', [ + 'presenter' => 'Home', + 'action' => 'default', + ]); + $router[] = $webModule; + + return $router; + } +} +``` + +--- + +## 14. Portal Presentery + +Fancyadmin poskytuje presenter traity pro portálovou část (admin): + +### BasePresenter + +```php +// app/UI/Portal/Presenters/BasePresenter.php + **Důležité:** import musí být eager (ne přes `AdtJsComponents.init`, který modul načítá lazy až +> když je formulář na stránce). Modul si při importu naváže delegovaný `change` listener na `document`, +> takže funguje i pro login formulář vložený přes AJAX (např. po odhlášení), aniž by se musel +> reinicializovat. Při lazy načtení by se po AJAX přepnutí na `/sign/in` listener nenavázal. + +**Závislost:** Projekt musí mít nainstalovaný npm balíček `keycloak-js`: +```bash +yarn add keycloak-js +``` + +### 18.7 Co se děje automaticky + +Po zapnutí Keycloak konfigurace fancyadmin automaticky: + +- **Registruje routy** `keycloak-auth/` a `keycloak-log/` v Portal modulu +- **Login formulář** — přidá `data-keycloak-check-url` atribut na email input; po zadání emailu JS zjistí SSO instanci z identity/role a přesměruje na odpovídající Keycloak +- **Logout** — `Sign:out` automaticky odhlásí i z Keycloaku (pokud se uživatel přihlásil přes SSO) +- **Frontend** — do layoutu injektuje `window.__keycloakSettings` pro keycloak-js adapter (silent SSO check, token refresh) +- **Registrace při SSO** — pokud se přes Keycloak přihlásí uživatel, který v aplikaci neexistuje, automaticky se mu vytvoří identita s vazbou na SSO instanci a `defaultRole` (pokud je nakonfigurovaná) + +### 18.8 Keycloak služba — správa uživatelů + +Keycloak instance jsou dostupné přes `KeycloakManager`: + +```php +$manager = $this->_fancyAdmin->getKeycloakManager(); // null pokud je Keycloak vypnutý + +// Získat konkrétní instanci podle názvu +$keycloak = $manager->getInstance('hlavni'); + +// Získat instanci podle identity (z identity.sso nebo role.sso) +$keycloak = $manager->getInstanceForIdentity($identity); + +// Získat instanci, přes kterou je přihlášen aktuální uživatel (ze session) +$keycloak = $manager->getInstanceFromSession(); +``` + +Každá instance poskytuje metody pro správu uživatelů přes Admin API: + +```php +// Registrace uživatele v Keycloaku (vrací existujícího pokud už existuje) +$keycloakUser = $keycloak->registerUser($identity, 'heslo', temporaryPassword: false); + +// Aktualizace údajů (email, jméno, příjmení) +$keycloakUser = $keycloak->updateUser($identity); + +// Deaktivace / aktivace +$keycloak->disableUser($identity); +$keycloak->enableUser($identity); + +// Nastavení hesla +$keycloak->setUserPassword($identity, 'noveHeslo', temporary: true); + +// Vyhledání uživatele podle emailu +$keycloakUser = $keycloak->findUser('user@example.com'); +``` + +### 18.9 Backchannel logout + +Keycloak podporuje backchannel logout — při ukončení session v Keycloaku (odhlášení, expirace, deaktivace uživatele) Keycloak pošle POST request na aplikaci, která invaliduje lokální session uživatele. + +#### Nastavení v Keycloaku + +V Keycloak admin panelu → **Clients** → váš confidential client → **Settings**: + +1. **Backchannel logout URL**: + ``` + https://admin.muj-projekt.cz/keycloak-auth/backchannel-logout?instance=nazev-sso + ``` + Kde `nazev-sso` odpovídá hodnotě `name` v tabulce `sso`. + +2. **Backchannel logout session required**: **On** + +Opakujte pro každou SSO instanci s odpovídajícím `?instance=` parametrem. + +#### Co se děje + +1. Keycloak pošle POST s `logout_token` (JWT) na backchannel URL +2. Aplikace dekóduje token, získá `sub` (Keycloak user ID) +3. Přes Admin API zjistí email uživatele +4. Najde lokální identitu podle emailu +5. Invaliduje všechny její sessions (`Authenticator::clearIdentity`) + +Tím je zajištěno, že: +- Uživatel odhlášený z Keycloaku je automaticky odhlášen i z aplikace +- Uživatel deaktivovaný v Keycloaku ztrácí přístup okamžitě (session je ukončena a nové SSO přihlášení selže) + +### 18.10 Přidání nové Keycloak instance + +Postup pro přidání další SSO instance do existujícího projektu: + +1. **DB** — vytvořte nový záznam v tabulce `sso` s kompletní konfigurací (realm, URL, credentials) +2. **DB** — u příslušných rolí/identit nastavte vazbu na nové SSO +3. **Keycloak** — v novém clientu nastavte backchannel logout URL (viz 18.9) + +Žádná změna PHP kódu, `.env` ani neon konfigurace není potřeba. Instance se vytváří dynamicky z databáze. + +### 18.11 Rozšíření chování + +Keycloak službu lze rozšířit v projektu — např. pro úpravu logiky vytváření identity při SSO loginu: + +```php +class MyKeycloak extends \ADT\FancyAdmin\Model\Security\Keycloak\Keycloak +{ + protected function createIdentity(array $userInfo): Identity + { + $identity = parent::createIdentity($userInfo); + // vlastní logika — přiřazení kontextu, notifikace, atd. + return $identity; + } +} +``` + +Pro použití vlastní třídy je potřeba rozšířit `KeycloakManager::createInstanceFromSso()` v projektu. + +--- + +## Shrnutí + +| Krok | Co | Proč | +|---|---|---| +| BaseEntity | Abstraktní třída s Identifier trait | Sdílený základ pro všechny entity | +| 9 entit | Identity, Account, Profile, AclRole, AclResource, Acl, Configuration, File, GridFilter | Fancyadmin vyžaduje všechny pro funkční ACL, auth, grid filtry, konfiguraci | +| AclResourceNameEnum | Enum implementující Nette\Security\Resource | Definice ACL resources pro fancyadmin config | +| BaseQuery + 6 Query tříd | QueryObject pattern s fancyadmin traits | Fancyadmin interně používá query factories pro přístup k datům | +| 6 QueryFactory interfaces | Rozšiřují fancyadmin factory interfaces | DI autowiring pro query třídy | +| Authenticator | Rozšiřuje OnetimeTokenAuthenticator | Autentizace přes Doctrine (email + heslo, OTP) | +| SecurityUser | Rozšiřuje ADT\DoctrineAuthenticator\SecurityUser | Session management, isAllowed(), isAdmin() | +| Permission | Rozšiřuje fancyadmin Permission | ACL authorizátor | +| EntityManager | Rozšiřuje ADT\DoctrineComponents\EntityManager | Rozšířený EntityManager s helper metodami | +| 3 Listeners | CreatedBy, AccountField, SelectAccount | Automatické nastavování created_by, account polí při persistu | +| Translator | Rozšiřuje Contributte\Translation\Translator | Překlady | +| RouterFactory | Integruje FancyAdminRouter | Sign routes, portal routes | +| Portal presentery | BasePresenter + AuthPresenter s fancyadmin traits | Admin layout, auth check, side panel | diff --git a/assets/js/_jquery-global.js b/assets/js/_jquery-global.js new file mode 100644 index 0000000..7fe6d68 --- /dev/null +++ b/assets/js/_jquery-global.js @@ -0,0 +1,12 @@ +// jQuery must be exposed as a global BEFORE any legacy jQuery plugin (jquery-ui-bundle, +// @regru/jquery-menu-aim, nette.ajax.js, …) is evaluated. ES module imports are hoisted +// and evaluated depth-first in source order, so importing this module FIRST guarantees +// the globals exist before those plugin modules run. +import jQuery from 'jquery'; + +window.$ = jQuery; +window.jquery = jQuery; +window.jQuery = jQuery; +globalThis.jQuery = jQuery; + +export default jQuery; diff --git a/assets/js/_registerFancyadminComponents.js b/assets/js/_registerFancyadminComponents.js new file mode 100644 index 0000000..48159b1 --- /dev/null +++ b/assets/js/_registerFancyadminComponents.js @@ -0,0 +1,13 @@ +// Registers FancyAdmin's own JS components (referenced as "~UI/..." in +// AdtJsComponents.init() calls), so each consuming project doesn't have to. +// +// Imported first by app.js — runs before app.js's init() calls, and merges into +// the shared registry (see ComponentLoader.registerModules), so the consumer can +// still register its own 'app' components and 'builtin' allowlist separately. +// +// The glob is relative to this file (assets/js/) → resolves to the package's src/UI. +import AdtJsComponents from 'adt-js-components'; + +AdtJsComponents.registerModules({ + fancyadmin: import.meta.glob('../../src/UI/**/index.js'), +}); diff --git a/assets/js/_registerStandaloneBuiltins.js b/assets/js/_registerStandaloneBuiltins.js new file mode 100644 index 0000000..da9d605 --- /dev/null +++ b/assets/js/_registerStandaloneBuiltins.js @@ -0,0 +1,10 @@ +// Built-in adt-js-components used by FancyAdmin's own standalone build (admin.js). +// In a real consuming project this allowlist lives in the project; here it's only +// for the package's standalone build. Separate module so it's evaluated before +// app.js's init() calls (static imports are hoisted, so a registerModules() call in +// admin.js's body would run too late). +import AdtJsComponents from 'adt-js-components'; + +AdtJsComponents.registerModules({ + builtin: import.meta.glob('../../node_modules/adt-js-components/src/{Messaging,Notifications,Translate}/index.js'), +}); diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..704b7ff --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,8 @@ +// Standalone Vite entry for the FancyAdmin package — the equivalent of the old +// webpack 'admin' build (which bundled assets/js/app.js). +// +// app.js self-registers FancyAdmin's own components ('fancyadmin' scope). For the +// standalone build we additionally register the built-ins it uses, first, so the +// registration runs before app.js's init() calls (imports are hoisted). +import './_registerStandaloneBuiltins'; +import './app'; diff --git a/assets/js/app.js b/assets/js/app.js index 84c85ba..55f609a 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,28 +1,31 @@ +// +// jQuery as a global — MUST be first so it is set before the legacy plugins below +// (and bundled deps like nette.ajax.js) are evaluated. Imports are hoisted. +// +import $ from './_jquery-global'; + +// +// Register FancyAdmin's own JS components (before the init() calls below). +// +import './_registerFancyadminComponents'; + // // SCSS styles // import '../scss/app.scss'; // -// Old non-modular JS vendor files +// Legacy non-modular jQuery plugins (rely on the global set above). // -// import jQuery from 'jquery'; -import $ from 'jquery'; import 'jquery-ui-bundle'; import '@regru/jquery-menu-aim'; -window.$ = $; -window.jquery = $; -window.jQuery = $; - -global.jQuery = $; - -import {Chart} from "chart.js/auto"; +// import {Chart} from "chart.js/auto"; import Nette from 'nette-forms'; import './dependentSelectBox' Nette.initOnLoad(); window.Nette = Nette; -window.Chart = Chart; +// window.Chart = Chart; import 'nette.ajax.js'; $.nette.init({ @@ -53,7 +56,7 @@ $.nette.ext('live').after(function($el) { $('[data-dependentselectbox]').dependentSelectBox(); }); -import 'daterangepicker'; +// import 'daterangepicker'; // // Modular vendor JS files @@ -80,20 +83,22 @@ import AdtJsComponents from 'adt-js-components'; // // AdtJsComponents.init('components-panels-base-baseChartPanel', 'UI/Portal/Components/Panels/Base/BaseChartPanelControl'); AdtJsComponents.init('select-account-form', '~UI/Components/Forms/SelectAccount'); +AdtJsComponents.init('portal-components-grids-traits-signInAsIdentity', '~UI/Components/Grids/Traits/SignInAsIdentity'); // AdtJsComponents.init('portal-components-forms-dashboardFilter', 'UI/Portal/Components/Forms/DashboardFilter'); // AdtJsComponents.init('portal-components-forms-changeLicenceForm', 'UI/Portal/Components/Forms/ChangeLicence'); // AdtJsComponents.init('portal-components-forms-warehouseOperationForm', 'UI/Portal/Components/Forms/WarehouseOperation'); // AdtJsComponents.init('companySitePlanDetail', 'UI/Portal/Presenters/CompanySitePlans'); // AdtJsComponents.init('dashboard', 'UI/Portal/Presenters/Dashboard'); // AdtJsComponents.init('dashboard', 'assets/js/dashboard'); -// AdtJsComponents.init('messaging', 'assets/js/messaging'); -// AdtJsComponents.init('notifications', 'assets/js/notifications'); -// AdtJsComponents.init('translate', 'assets/js/translate'); +AdtJsComponents.init('messaging', 'Messaging'); +AdtJsComponents.init('notifications', 'Notifications'); +AdtJsComponents.init('translate', 'Translate'); + // AdtJsComponents.init('print-dashboard', 'assets/js/printDashboard'); // AdtJsComponents.init('safari-support', 'assets/js/safariSupport'); // // import './netteForm'; -// import './flashes'; +import './flashes'; // import './userDropdown'; // import './tableActionsShadow'; // import './_datagrid'; @@ -106,3 +111,6 @@ AdtJsComponents.init('select-account-form', '~UI/Components/Forms/SelectAccount' // import 'forms-replicator'; import './sideMenu' +import './datagrid/datagrid' +import './datagrid' +import './sidePanel' diff --git a/assets/js/datagrid.js b/assets/js/datagrid.js new file mode 100644 index 0000000..f349ac8 --- /dev/null +++ b/assets/js/datagrid.js @@ -0,0 +1,105 @@ +// Explicit global: ublaboo renders inline scripts that call datagridSortable(). +// ESM modules are strict, so the previous implicit global assignment threw. +window.datagridSortable = function($el) { + if (typeof $.fn.sortable === 'undefined') { + return; + } + return $el.find('.datagrid [data-sortable]').sortable({ + handle: '.handle-sort', + items: 'tr', + axis: 'y', + update: function(event, ui) { + var component_prefix, data, item_id, next_id, prev_id, row, url; + row = ui.item.closest('tr[data-id]'); + item_id = row.data('id'); + prev_id = null; + next_id = null; + if (row.prev().length) { + prev_id = row.prev().data('id'); + } + if (row.next().length) { + next_id = row.next().data('id'); + } + url = $(this).data('sortable-url'); + data = {}; + component_prefix = row.closest('.datagrid').find('tbody').attr('data-sortable-parent-path'); + data[(component_prefix + '-item_id').replace(/^-/, '')] = item_id; + if (prev_id !== null) { + data[(component_prefix + '-prev_id').replace(/^-/, '')] = prev_id; + } + if (next_id !== null) { + data[(component_prefix + '-next_id').replace(/^-/, '')] = next_id; + } + return $.nette.ajax({ + type: 'GET', + url: url, + data: data, + error: function(jqXHR, textStatus, errorThrown) { + return alert(jqXHR.statusText); + } + }); + }, + helper: function(e, ui) { + ui.children().each(function() { + return $(this).width($(this).width()); + }); + return ui; + } + }); +}; + +$.nette.ext('live').after(function (el) { + return datagridSortable(el); +}); + +$(document).on('hidden.bs.collapse', '.datagrid form > .collapse', (e) => { + $(e.currentTarget).closest('form').find('.reset-filter').click(); +}); + +/** + * Funkce zajistuje proklik na detail, pokud je na radku jenom jeden sloupec s odkazem ve sloupci col-name (musime mit + * name pokazde i kdyby slo o id nebo jiny identifikator) Je klikatelny cely radek az na bunky obsahujici tlacitka, selecty + * nebo inputy, stejne tak ignoruje sloupec col-action + */ +$(document).on('click', '.click-line-detail table tbody td:not(.col-action):not(:has(button, input, select))', (e) => { + $(e.currentTarget).closest('tr').find('.col-name > a').click(); +}); + +/** + * Funkce zajistuje proklik na detail kery je uvedeny ve sloupci col-name (musime mit name pokazde i kdyby slo + * o id nebo jiny identifikator) a ignoruje kliknuti na bunky obsahujici odkaz, select, tlacitka ci inputy, steje tak + * ignoruje sloupec col-action. Sloupcum s odkazem (i col-name) je treba pridat tridu click-line-detail-no-link-im-the-link. + */ +$(document).on('click', '.click-line-detail-no-link table tbody td:not(.col-action):not(:has(a, button, input, select))', (e) => { + $(e.currentTarget).closest('tr').find('.col-name > a').click(); +}); + +/** + * Funkce slouzi k prokliku pres bunku, ktera obsahuje link. Napriklad pokud budeme mit moznost se prokliknout jak na detail + * zarizeni, tak platby. Musime ale na bunku dat classu click-line-detail-no-link-im-the-link + */ +$(document).on('click', '.click-line-detail-no-link-im-the-link', (e) => { + $(e.currentTarget).find('a').click(); +}); + +$(document).on('click', 'a.link-confirmation', function (e) { + if (!confirm($(this).data('confirm-text'))){ + event.preventDefault(); + } +}); + +$(document).on('click', 'tbody tr:not(.row-item-detail) td:not(.col-action)', function (event) { + if (event.target.tagName.toLowerCase() === 'a' || $(event.target).hasClass('menu-open-button')) { + return; // Klik byl na odkaz, takže nevyvoláváme žádnou akci. + } + + let $tr = $(this).parent(); + + if ($tr.next().hasClass('toggled')) { + $tr.next().removeClass('toggled'); + $tr.next().find('.item-detail-content').css('display', 'none'); + } else { + $tr.next().addClass('toggled'); + $tr.next().find('.item-detail-content').css('display', 'block'); + } +}); \ No newline at end of file diff --git a/assets/js/datagrid/datagrid-instant-url-refresh.js b/assets/js/datagrid/datagrid-instant-url-refresh.js new file mode 100644 index 0000000..f4ae43c --- /dev/null +++ b/assets/js/datagrid/datagrid-instant-url-refresh.js @@ -0,0 +1,30 @@ +var dataGridRegisterAjaxCall; + +if (typeof naja !== "undefined") { + dataGridRegisterAjaxCall = function (params) { + var method = params.type || 'GET'; + var data = params.data || null; + + naja.makeRequest(method, params.url, data, { + history: 'replace' + }) + .then(params.success) + .catch(params.error); + }; + +} else { + dataGridRegisterAjaxCall = function (params) { + $.nette.ajax(params); + }; +} + +document.addEventListener('DOMContentLoaded', function () { + var element = document.querySelector('.datagrid'); + + if (element !== null) { + return dataGridRegisterAjaxCall({ + type: 'GET', + url: element.getAttribute('data-refresh-state') + }); + } +}); diff --git a/assets/js/datagrid/datagrid-spinners.css b/assets/js/datagrid/datagrid-spinners.css new file mode 100755 index 0000000..818714d --- /dev/null +++ b/assets/js/datagrid/datagrid-spinners.css @@ -0,0 +1,118 @@ +@keyframes ublaboo-spinner-icon { + 0% { + transform: rotate(0); } + 50% { + transform: rotate(180deg); } + 100% { + transform: rotate(360deg); } } + +@-webkit-keyframes ublaboo-spinner-icon { + 0% { + transform: rotate(0); } + 50% { + transform: rotate(180deg); } + 100% { + transform: rotate(360deg); } } + +.ublaboo-spinner-icon > span { + animation-duration: 2s; + animation-delay: 0; + animation-iteration-count: infinite; + animation-timing-function: ease; + animation-name: ublaboo-spinner-icon; } + +@keyframes ublaboo-spinner-small { + 0% { + transform: translate(21.3px, 2.2px); } + 11.1% { + transform: translate(8.1px, 25.2px); } + 22.2% { + transform: translate(12.7px, -0.7px); } + 33.3% { + transform: translate(17.2px, 25.2px); } + 44.4% { + transform: translate(4.2px, 2.2px); } + 55.5% { + transform: translate(24.1px, 19.5px); } + 66.6% { + transform: translate(-0.3px, 10.3px); } + 77.7% { + transform: translate(25.8px, 10.3px); } + 88.8% { + transform: translate(1.2px, 19.3px); } + 100% { + transform: translate(21.3px, 2.2px); } } + +@-webkit-keyframes ublaboo-spinner-small { + 0% { + transform: translate(21.3px, 2.2px); } + 11.1% { + transform: translate(8.1px, 25.2px); } + 22.2% { + transform: translate(12.7px, -0.7px); } + 33.3% { + transform: translate(17.2px, 25.2px); } + 44.4% { + transform: translate(4.2px, 2.2px); } + 55.5% { + transform: translate(24.1px, 19.5px); } + 66.6% { + transform: translate(-0.3px, 10.3px); } + 77.7% { + transform: translate(25.8px, 10.3px); } + 88.8% { + transform: translate(1.2px, 19.3px); } + 100% { + transform: translate(21.3px, 2.2px); } } + +@keyframes ublaboo-spinner-in { + 0% { + opacity: 0; } + 100% { + opacity: 1; } } + +@-webkit-keyframes ublaboo-spinner-in { + 0% { + opacity: 0; } + 100% { + opacity: 1; } } + +.ublaboo-spinner { + line-height: 0; + display: inline-block; + margin: auto; + position: relative; + margin: 0 1em -11px 1em; + top: 1px; + opacity: 0; + animation-duration: 150ms; + animation-delay: 0; + animation-iteration-count: 1; + animation-timing-function: ease-in; + animation-name: ublaboo-spinner-in; + animation-fill-mode: forwards; } + .ublaboo-spinner > i { + position: absolute; + background-color: #37434f; + left: 0; + top: 0; + animation-duration: 6s; + animation-delay: 0; + animation-iteration-count: infinite; + animation-timing-function: ease; } + .ublaboo-spinner > i:nth-of-type(2) { + animation-delay: -1.5s; } + .ublaboo-spinner > i:nth-of-type(3) { + animation-delay: -3s; } + .ublaboo-spinner > i:nth-of-type(4) { + animation-delay: -4.5s; } + .ublaboo-spinner.ublaboo-spinner-small { + width: 28.0px; + height: 28.0px; } + .ublaboo-spinner.ublaboo-spinner-small > i { + width: 4.0px; + height: 4.0px; + border-radius: 2px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + animation-name: ublaboo-spinner-small; } diff --git a/assets/js/datagrid/datagrid-spinners.js b/assets/js/datagrid/datagrid-spinners.js new file mode 100644 index 0000000..b6a0b2b --- /dev/null +++ b/assets/js/datagrid/datagrid-spinners.js @@ -0,0 +1,89 @@ +var dataGridRegisterExtension; + +if (typeof naja !== "undefined") { + var isNaja2 = function () { return naja && naja.VERSION && naja.VERSION >= 2 }; + var najaEventParams = function (params) { return isNaja2() ? params.detail : params }; + var najaRequest = function (params) { return isNaja2() ? params.detail.request : params.xhr }; + dataGridRegisterExtension = function (name, extension) { + var init = extension.init; + var success = extension.success; + var before = extension.before; + var complete = extension.complete; + + + var NewExtension = function NewExtension(naja, name) { + this.name = name; + + this.initialize = function (naja) { + if(init) { + naja.addEventListener('init', function (params) { + init(najaEventParams(params).defaultOptions); + }); + } + + if(success) { + naja.addEventListener('success', function (params) { + var payload = isNaja2() ? params.detail.payload : params.response; + success(payload, najaEventParams(params).options); + }); + } + + if(before) { + naja.addEventListener('before', function (params) { + before(najaRequest(params), najaEventParams(params).options); + }); + } + + if(complete) { + naja.addEventListener('complete', function (params) { + complete(najaRequest(params), najaEventParams(params).options); + }); + } + } + if (!isNaja2()) { + this.initialize(naja); + } + return this; + } + + if (isNaja2()) { + naja.registerExtension(new NewExtension(null, name)); + } else { + naja.registerExtension(NewExtension, name); + } + }; +} else if ($.nette) { + dataGridRegisterExtension = function (name, extension) { + $.nette.ext(name, extension); + }; +} + +dataGridRegisterExtension('ublaboo-spinners', { + before: function(xhr, settings) { + var el, id, row_detail, spinner_template, grid_fullname; + if (settings.nette) { + el = settings.nette.el; + spinner_template = $('
'); + if (el.is('.datagrid [name="group_action[submit]"]')) { + return el.after(spinner_template); + } else if (el.is('.datagrid a') && el.data('toggle-detail')) { + id = settings.nette.el.attr('data-toggle-detail'); + grid_fullname = settings.nette.el.attr('data-toggle-detail-grid-fullname'); + row_detail = $('.item-detail-' + grid_fullname + '-id-' + id); + if (!row_detail.hasClass('loaded')) { + return el.addClass('ublaboo-spinner-icon'); + } + } else if (el.is('.datagrid .col-pagination a')) { + return el.closest('.row-grid-bottom').find('.col-per-page').prepend(spinner_template); + } else if (el.is('.datagrid .datagrid-per-page-submit')) { + return el.closest('.row-grid-bottom').find('.col-per-page').prepend(spinner_template); + } else if (el.is('.datagrid .reset-filter')) { + return el.closest('.row-grid-bottom').find('.col-per-page').prepend(spinner_template); + } + } + }, + complete: function() { + $('.ublaboo-spinner').remove(); + return $('.ublaboo-spinner-icon').removeClass('ublaboo-spinner-icon'); + } +}); diff --git a/assets/js/datagrid/datagrid.css b/assets/js/datagrid/datagrid.css new file mode 100755 index 0000000..30c58a6 --- /dev/null +++ b/assets/js/datagrid/datagrid.css @@ -0,0 +1,641 @@ +@keyframes edited { + 0% { + background-color: #A6E2A9 + } + + 100% { + background-color: transparent + } + +} + +@keyframes edited-error { + 0% { + background-color: #E8AAA4 + } + + 100% { + background-color: transparent + } + +} + +.datagrid { + background-color: #fff; + padding: 1em; + box-sizing: border-box +} + +.datagrid .datagrid-input-group-full-width { + width: 100% +} + +.datagrid .hidden { + display: none !important +} + +.datagrid .datagrid-collapse-filters-button-row { + margin-bottom: 0.5em +} + +.datagrid .col-action .dropdown { + display: inline-block +} + +.datagrid .datagrid-row-inline-add.datagrid-row-inline-add-hidden { + display: none +} + +.datagrid .datagrid-row-columns-summary td { + border-top: 2px solid #bbb; + border-left: 1px solid #eee; + border-right: 1px solid #eee; + font-weight: bold +} + +.datagrid .datagrid-row-columns-summary td:first-child { + border-left: 1px solid #ddd +} + +.datagrid .datagrid-row-columns-summary td:last-child { + border-right: 1px solid #ddd +} + +.datagrid .datagrid-toolbar { + margin-top: .35em; + float: right; + display: inline-block +} + +.datagrid .datagrid-toolbar > div > span { + margin-left: 1em +} + +.datagrid .datagrid-toolbar > div > span > a { + margin-left: 0.5em +} + +.datagrid .datagrid-toolbar > div { + display: inline-block +} + +.datagrid-toolbar .fa-square, .datagrid-toolbar .fa-check-square { + font-weight: normal; +} + +.datagrid .datagrid-exports .btn { + margin-left: 0.5em +} + +.datagrid .datagrid-exports .btn:first-child { + margin-left: 0 +} + +.datagrid .datagrid-settings { + display: inline-block +} + +.datagrid .datagrid-settings .dropdown-menu--grid { + font-size: 12px +} + +.datagrid .datagrid-settings .dropdown-menu--grid li .fa { + margin-right: 0.5em +} + +.datagrid .row-reset-filter { + text-align: right; + margin-bottom: 0.5em +} + +.datagrid .row-filters .datagrid-row-outer-filters-group { + margin-bottom: 0.5em +} + +.datagrid .datagrid-manual-submit { + margin-bottom: 0.5em +} + +.datagrid .filter-range-delimiter { + text-align: center +} + +.datagrid .bootstrap-select.input-sm > .btn { + padding: 5px 25px 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px +} + +.datagrid table { + margin: 0 +} + +.datagrid table thead tr .bootstrap-select:not([class*=col-]):not(.input-group-btn) { + width: auto +} + +.datagrid table thead tr .bootstrap-select:not([class*=col-]):not(.input-group-btn) > .btn { + width: auto +} + +.datagrid table thead tr .bootstrap-select:not([class*=col-]):not(.input-group-btn) .dropdown-menu li { + font-size: 13px +} + +.datagrid table thead tr.row-group-actions th { + border-bottom-width: 0 !important; + background-color: #f9f9f9 +} + +.datagrid table thead tr.row-group-actions .datagrid-selected-rows-count { + margin-left: 0.3em +} + +.datagrid table thead tr th { + font-size: 90%; + vertical-align: top +} + +.datagrid table thead tr th hr { + margin: 8px -8px +} + +.datagrid table thead tr th .datagrid-column-header-additions { + float: right +} + +.datagrid table thead tr th .datagrid-column-header-additions a[data-datagrid-reset-filter-by-column] { + margin-left: 0.3em; + color: #858585 +} + +.datagrid table thead tr th .datagrid-column-header-additions .column-settings-menu { + opacity: 0; + cursor: pointer; + margin-left: 0.3em; + display: inline-block +} + +.datagrid table thead tr th .datagrid-column-header-additions .column-settings-menu .dropdown-menu { + font-size: 12px +} + +.datagrid table thead tr th .datagrid-column-header-additions .column-settings-menu .dropdown-menu li .fa { + margin-right: 0.5em +} + +.datagrid table thead tr th .datagrid-column-header-additions .column-settings-menu .dropdown-toggle::after { + display: none !important +} + +.datagrid .datagrid-col-filter-date-range { + width: auto; + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; +} + +.datagrid .datagrid-col-filter-date-range > .input-group { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; +} + +.datagrid .datagrid-col-filter-datte-range-delimiter { + background-color: inherit; + border: none; + padding: .25rem .5rem +} + +.datagrid table thead tr th .datagrid-col-filter-range .form-control { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px +} + +.datagrid table thead tr th:hover .column-settings-menu { + opacity: 1 +} + +.datagrid table tbody td { + vertical-align: middle +} + +.datagrid table tbody tr.ui-sortable-helper { + display: table +} + +.datagrid table tbody tr.row-item-detail { + display: none +} + +.datagrid table tbody tr.row-item-detail.toggled { + display: table-row +} + +.datagrid table tbody tr.row-item-detail .item-detail-content { + display: none +} + +.datagrid table tbody tr.row-item-detail-helper { + display: none +} + +.datagrid table tbody tr .datagrid-inline-edit .form-control { + margin: -3px; + padding-bottom: 4px; + padding-top: 4px; + height: 28px +} + +.datagrid table tbody tr td[data-datagrid-editable-url].editing textarea { + padding: 2px; + margin: -3px +} + +.datagrid table tbody tr td.edited { + animation-name: edited; + animation-duration: 1.2s; + animation-delay: 0 +} + +.datagrid table tbody tr td.edited-error { + animation-name: edited-error; + animation-duration: 1.6s; + animation-delay: 0 +} + +.datagrid table th.col-checkbox, .datagrid table td.col-checkbox { + padding: 0; + width: 2.1em; + text-align: center; + vertical-align: middle +} + +.datagrid table th.col-checkbox .happy-checkbox, .datagrid table td.col-checkbox .happy-checkbox { + margin-right: 0 +} + +.datagrid table th.col-checkbox.col-checkbox-first, .datagrid table td.col-checkbox.col-checkbox-first { + border-top-color: transparent +} + +.datagrid table th.col-checkbox { + background-color: #f9f9f9 +} + +.datagrid table th.col-action, .datagrid table td.col-action { + white-space: nowrap; + width: 10px +} + +.datagrid table th.col-action { + text-align: center +} + +.datagrid table td.col-action { + text-align: right +} + +.datagrid table th.datagrid-fit-content, .datagrid table td.datagrid-fit-content { + width: 1%; + white-space: nowrap +} + +.datagrid .datagrid-tree > .datagrid-tree-header .datagrid-tree-item-right-actions-action { + opacity: 0 +} + +.datagrid .datagrid-tree > .datagrid-tree-item { + margin-left: 20px +} + +.datagrid .datagrid-tree .datagrid-tree-item { + position: relative +} + +.datagrid .datagrid-tree .datagrid-tree-item.ui-sortable-placeholder { + visibility: visible !important; + background-color: rgba(70, 83, 93, 0.1) +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content { + position: relative; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + box-sizing: border-box; + height: 37px; + box-shadow: inset 0px -1px 1px -1px #9B9B9B +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left, .datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left { + order: 1 +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron { + -webkit-border-radius: 11px; + -moz-border-radius: 11px; + border-radius: 11px; + width: 22px; + height: 22px; + line-height: 20px; + vertical-align: middle; + background-color: #fff; + display: inline-block; + text-align: center; + position: relative; + margin: 0 5px 0 -27px; + transition: transform 0.2s ease-in-out +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron:hover { + -webkit-box-shadow: 0px 0px 3px 0px #b4b4b4; + -moz-box-shadow: 0px 0px 3px 0px #b4b4b4; + box-shadow: 0px 0px 3px 0px #b4b4b4 +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron.toggle-rotate { + transform: rotate(90deg) +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron .fa { + font-size: 10px; + transform: translate(1px, 0) +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right { + position: relative; + order: 2; + flex-basis: 50%; + display: flex; + flex-wrap: nowrap; + justify-content: flex-end; + flex-direction: row +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right .btn { + margin-top: -3px +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right .datagrid-tree-item-right-columns { + white-space: nowrap; + display: flex; + flex-basis: 70%; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-end +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right .datagrid-tree-item-right-columns .datagrid-tree-item-right-columns-column { + padding: 0 7px; + margin-right: 4px; + flex-basis: 25% +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right .datagrid-tree-item-right-columns .datagrid-tree-item-right-columns-column:last-child { + margin-right: 0 +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right .datagrid-tree-item-right-actions { + margin-left: 7px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right .datagrid-tree-item-right-actions .datagrid-tree-item-right-actions-action { + margin-right: 4px +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right .datagrid-tree-item-right-actions .datagrid-tree-item-right-actions-action:last-child { + margin-right: 0 +} + +.datagrid .datagrid-tree .datagrid-tree-item .datagrid-tree-item-children:not(.datagrid-tree) { + margin-left: 28px +} + +.datagrid .datagrid-tree .datagrid-tree-item:not(.has-children) > .datagrid-tree-item-children { + box-sizing: border-box; + position: relative; + width: calc(100% - 28px); + min-height: 9px; + margin-top: -9px +} + +.datagrid .datagrid-tree .datagrid-tree-item.has-children > .datagrid-tree-item-children { + display: none +} + +.datagrid .datagrid-tree .datagrid-tree-item.has-children > .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 14px) +} + +.datagrid .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 14px) +} + +.datagrid .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 28px) !important +} + +.datagrid .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 42px) !important +} + +.datagrid .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 56px) !important +} + +.datagrid .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 74px) !important +} + +.datagrid .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 88px) !important +} + +.datagrid .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 102px) !important +} + +.datagrid .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-children .datagrid-tree-item-right { + flex-basis: calc(50% + 116px) !important +} + +.datagrid .btn { + transition: all 0.1s ease-in-out; + white-space: nowrap +} + +.datagrid select { + padding: 0; + text-transform: none +} + +.datagrid .row-grid-bottom { + font-size: 0; + padding: 8px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-top: 0 +} + +.datagrid .row-grid-bottom .col-items { + font-size: 14px; + display: inline-block; + width: 25% +} + +.datagrid .row-grid-bottom .col-pagination { + font-size: 14px; + display: inline-block; + width: 50% +} + +.datagrid .row-grid-bottom .col-per-page { + font-size: 14px; + display: inline-block; + width: 25% +} + +.datagrid .row-grid-bottom .col-per-page form { + display: inline-block +} + +.datagrid .row-grid-bottom .col-per-page .form-control { + width: auto; + display: inline-block +} + +.datagrid .row-grid-bottom .datagrid-per-page-submit { + position: absolute; + visibility: hidden; + width: 0; + top: -200px +} + +.datagrid .pagination.active > span { + color: #fff +} + +.datagrid .pagination > a.disabled { + color: #989898; + cursor: not-allowed +} + +.datagrid .pagination > a.active { + pointer-events: none; + cursor: default +} + +.datagrid .row-group-actions th { + font-weight: normal +} + +.datagrid .col-checkbox { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none +} + +.datagrid .col-checkbox .happy-checkbox { + margin-top: 2px +} + +.datagrid .datagrid-column-status-option-icon { + float: right +} + +@media (min-width:768px) { + .datagrid .ublaboo-datagrid-th-form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle + } + + .datagrid .ublaboo-datagrid-th-form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle + } + + .datagrid .ublaboo-datagrid-th-form-inline .form-control[hidden] { + display:none; + } + + .ublaboo-datagrid-th-form-inline .form-control[hidden] { + display: none; + } + + .datagrid .ublaboo-datagrid-th-form-inline .input-group { + display: inline-table; + vertical-align: middle + } + + .datagrid .ublaboo-datagrid-th-form-inline .input-group .form-control { + width: auto + } + + .datagrid .ublaboo-datagrid-th-form-inline .input-group > .form-control { + width: 100% + } + + .datagrid .input-group-text { + height: calc(1.5em + 0.5rem + 2px); + } + + .datagrid .ublaboo-datagrid-th-form-inline .control-label { + margin-bottom: 0; + vertical-align: middle + } + + .datagrid .ublaboo-datagrid-th-form-inline .radio, .datagrid .ublaboo-datagrid-th-form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle + } + + .datagrid .ublaboo-datagrid-th-form-inline .radio label, .datagrid .ublaboo-datagrid-th-form-inline .checkbox label { + padding-left: 0 + } + + .datagrid .ublaboo-datagrid-th-form-inline .radio input[type="radio"], .datagrid .ublaboo-datagrid-th-form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0 + } + +} + +.datagrid .btn-xs, .datagrid .btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px +} + +.datagrid .dropdown-item { + padding: 3px 20px; + line-height: 1.42857143; + font-size: 12px; +} diff --git a/assets/js/datagrid/datagrid.js b/assets/js/datagrid/datagrid.js new file mode 100644 index 0000000..83d9f80 --- /dev/null +++ b/assets/js/datagrid/datagrid.js @@ -0,0 +1,928 @@ +var dataGridRegisterExtension, dataGridRegisterAjaxCall, dataGridLoad, dataGridSubmitForm; + +if (typeof naja !== "undefined") { + var isNaja2 = function () { return naja && naja.VERSION && naja.VERSION >= 2 }; + var najaEventParams = function (params) { return isNaja2() ? params.detail : params }; + var najaRequest = function (params) { return isNaja2() ? params.detail.request : params.xhr }; + dataGridRegisterExtension = function (name, extension) { + var init = extension.init; + var success = extension.success; + var before = extension.before; + var complete = extension.complete; + var interaction = extension.interaction; + + + var NewExtension = function NewExtension(naja, name) { + this.name = name; + + this.initialize = function (naja) { + if(init) { + naja.addEventListener('init', function (params) { + init(najaEventParams(params).defaultOptions); + }); + } + + if(success) { + naja.addEventListener('success', function (params) { + var payload = isNaja2() ? params.detail.payload : params.response; + success(payload, najaEventParams(params).options); + }); + } + + var interactionTarget = naja; + if (isNaja2()) { + interactionTarget = interactionTarget.uiHandler; + } + + interactionTarget.addEventListener('interaction', function (params) { + if (isNaja2()) { + params.detail.options.nette = { + el: $(params.detail.element) + } + } else { + params.options.nette = { + el: $(params.element) + } + } + if (interaction) { + if (!interaction(najaEventParams(params).options)){ + params.preventDefault(); + } + } + }); + + if(before) { + naja.addEventListener('before', function (params) { + if (!before(najaRequest(params), najaEventParams(params).options)) + params.preventDefault(); + }); + } + + if(complete) { + naja.addEventListener('complete', function (params) { + complete(najaRequest(params), najaEventParams(params).options); + }); + } + } + if (!isNaja2()) { + this.initialize(naja); + } + return this; + } + + if (isNaja2()) { + naja.registerExtension(new NewExtension(null, name)); + } else { + naja.registerExtension(NewExtension, name); + } + }; + + + dataGridRegisterAjaxCall = function (params) { + var method = params.type || 'GET'; + var data = params.data || null; + + naja.makeRequest(method, params.url, data, {}) + .then(params.success) + .catch(params.error); + }; + + dataGridLoad = function () { + naja.load(); + }; + + dataGridSubmitForm = function (form) { + return naja.uiHandler.submitForm(form.get(0)); + }; +} else if ($.nette) { + dataGridRegisterExtension = function (name, extension) { + $.nette.ext(name, extension); + }; + dataGridRegisterAjaxCall = function (params) { + $.nette.ajax(params); + }; + dataGridLoad = function () { + $.nette.load(); + }; + dataGridSubmitForm = function (form) { + return form.submit(); + }; +} else { + throw new Error("Include Naja.js or nette.ajax for datagrids to work!") +} + + +var datagridFitlerMultiSelect, datagridGroupActionMultiSelect, datagridShiftGroupSelection, datagridSortable, datagridSortableTree, getEventDomPath, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +$(document).on('click', '[data-datagrid-confirm]:not(.ajax)', function(e) { + if (!confirm($(e.target).closest('a').attr('data-datagrid-confirm'))) { + e.stopPropagation(); + return e.preventDefault(); + } +}); + +if (typeof naja !== "undefined") { + dataGridRegisterExtension('datagrid.confirm', { + interaction: function(settings) { + var confirm_message; + if (settings.nette) { + confirm_message = settings.nette.el.data('datagrid-confirm'); + if (confirm_message) { + return confirm(confirm_message); + } + } + return true; + } + }); +} else { + dataGridRegisterExtension('datagrid.confirm', { + before: function(xhr, settings) { + var confirm_message; + if (settings.nette) { + confirm_message = settings.nette.el.data('datagrid-confirm'); + if (confirm_message) { + return confirm(confirm_message); + } + } + return true; + } + }); +} + + +$(document).on('change', 'select[data-autosubmit-per-page]', function() { + var button; + button = $(this).parent().find('input[type=submit]'); + if (button.length === 0) { + button = $(this).parent().find('button[type=submit]'); + } + return button.click(); +}).on('change', 'select[data-autosubmit]', function() { + return dataGridSubmitForm($(this).closest('form').first()); +}).on('change', 'input[data-autosubmit][data-autosubmit-change]', function(e) { + var $this, code; + code = e.which || e.keyCode || 0; + clearTimeout(window.datagrid_autosubmit_timer); + $this = $(this); + return window.datagrid_autosubmit_timer = setTimeout((function(_this) { + return function() { + return dataGridSubmitForm($this.closest('form').first()); + }; + })(this), 200); +}).on('keyup', 'input[data-autosubmit]', function(e) { + var $this, code; + code = e.which || e.keyCode || 0; + if ((code !== 13) && ((code >= 9 && code <= 40) || (code >= 112 && code <= 123))) { + return; + } + clearTimeout(window.datagrid_autosubmit_timer); + $this = $(this); + return window.datagrid_autosubmit_timer = setTimeout((function(_this) { + return function() { + return dataGridSubmitForm($this.closest('form').first()); + }; + })(this), 200); +}).on('keydown', '.datagrid-inline-edit input', function(e) { + var code; + code = e.which || e.keyCode || 0; + if (code === 13) { + e.stopPropagation(); + e.preventDefault(); + return $(this).closest('tr').find('.col-action-inline-edit [name="inline_edit[submit]"]').click(); + } +}); + +$(document).on('keydown', 'input[data-datagrid-manualsubmit]', function(e) { + var code; + code = e.which || e.keyCode || 0; + if (code === 13) { + e.stopPropagation(); + e.preventDefault(); + return dataGridSubmitForm($(this).closest('form').first()); + } +}); + +getEventDomPath = function(e) { + var node, path; + if (indexOf.call(e, path) >= 0) { + return e.path; + } + path = []; + node = e.target; + while (node !== document.body) { + if (node === null) { + break; + } + path.push(node); + node = node.parentNode; + } + return path; +}; + +datagridShiftGroupSelection = function() { + var last_checkbox; + last_checkbox = null; + return document.addEventListener('click', function(e) { + var checkboxes_rows, current_checkbox_row, el, event, i, ie, input, j, k, last_checkbox_row, last_checkbox_tbody, len, len1, len2, ref, ref1, results, row, rows; + ref = getEventDomPath(e); + for (i = 0, len = ref.length; i < len; i++) { + el = ref[i]; + if ($(el).is('.col-checkbox') && last_checkbox && e.shiftKey) { + current_checkbox_row = $(el).closest('tr'); + last_checkbox_row = last_checkbox.closest('tr'); + last_checkbox_tbody = last_checkbox_row.closest('tbody'); + checkboxes_rows = last_checkbox_tbody.find('tr').toArray(); + if (current_checkbox_row.index() > last_checkbox_row.index()) { + rows = checkboxes_rows.slice(last_checkbox_row.index(), current_checkbox_row.index()); + } else if (current_checkbox_row.index() < last_checkbox_row.index()) { + rows = checkboxes_rows.slice(current_checkbox_row.index() + 1, last_checkbox_row.index()); + } + if (!rows) { + return; + } + for (j = 0, len1 = rows.length; j < len1; j++) { + row = rows[j]; + input = $(row).find('.col-checkbox input[type=checkbox]')[0]; + if (input) { + input.checked = true; + ie = window.navigator.userAgent.indexOf("MSIE "); + if (ie) { + event = document.createEvent('Event'); + event.initEvent('change', true, true); + } else { + event = new Event('change', { + 'bubbles': true + }); + } + input.dispatchEvent(event); + } + } + } + } + ref1 = getEventDomPath(e); + results = []; + for (k = 0, len2 = ref1.length; k < len2; k++) { + el = ref1[k]; + if ($(el).is('.col-checkbox')) { + results.push(last_checkbox = $(el)); + } else { + results.push(void 0); + } + } + return results; + }); +}; + +datagridShiftGroupSelection(); + +document.addEventListener('change', function(e) { + var buttons, checked_inputs, counter, event, grid, i, ie, input, inputs, len, results, select, total; + grid = e.target.getAttribute('data-check'); + if (grid) { + checked_inputs = document.querySelectorAll('input[data-check-all-' + grid + ']:checked'); + select = document.querySelector('.datagrid-' + grid + ' select[name="group_action[group_action]"]'); + buttons = document.querySelectorAll('.datagrid-' + grid + ' .row-group-actions *[type="submit"]'); + counter = document.querySelector('.datagrid-' + grid + ' .datagrid-selected-rows-count'); + + if (checked_inputs.length) { + if (buttons) { + buttons.forEach(function (button) { + button.disabled = false; + }); + } + if (select) { + select.disabled = false; + } + total = document.querySelectorAll('input[data-check-all-' + grid + ']').length; + if (counter) { + counter.innerHTML = checked_inputs.length + '/' + total; + } + } else { + if (buttons) { + buttons.forEach(function (button) { + button.disabled = true; + }); + } + if (select) { + select.disabled = true; + select.value = ""; + } + if (counter) { + counter.innerHTML = ""; + } + } + ie = window.navigator.userAgent.indexOf("MSIE "); + if (ie) { + event = document.createEvent('Event'); + event.initEvent('change', true, true); + } else { + event = new Event('change', { + 'bubbles': true + }); + } + if (select) { + select.dispatchEvent(event); + } + } + grid = e.target.getAttribute('data-check-all'); + if (grid) { + inputs = document.querySelectorAll('input[type=checkbox][data-check-all-' + grid + ']'); + results = []; + for (i = 0, len = inputs.length; i < len; i++) { + input = inputs[i]; + input.checked = e.target.checked; + ie = window.navigator.userAgent.indexOf("MSIE "); + if (ie) { + event = document.createEvent('Event'); + event.initEvent('change', true, true); + } else { + event = new Event('change', { + 'bubbles': true + }); + } + results.push(input.dispatchEvent(event)); + } + return results; + } +}); + + +window.datagridSerializeUrl = function(obj, prefix) { +var str = []; +for(var p in obj) { + if (obj.hasOwnProperty(p)) { + var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p]; + if (v !== null && v !== "") { + if (typeof v == "object") { + var r = window.datagridSerializeUrl(v, k); + if (r) { + str.push(r); + } + } else { + str.push(encodeURIComponent(k) + "=" + encodeURIComponent(v)); + } + } + } +} +return str.join("&"); +} +; + +datagridSortable = function() { + if (typeof $.fn.sortable === 'undefined') { + return; + } + return $('.datagrid [data-sortable]').sortable({ + handle: '.handle-sort', + items: 'tr', + axis: 'y', + update: function(event, ui) { + var component_prefix, data, item_id, next_id, prev_id, row, url; + row = ui.item.closest('tr[data-id]'); + item_id = row.data('id'); + prev_id = null; + next_id = null; + if (row.prev().length) { + prev_id = row.prev().data('id'); + } + if (row.next().length) { + next_id = row.next().data('id'); + } + url = $(this).data('sortable-url'); + data = {}; + component_prefix = row.closest('.datagrid').find('tbody').attr('data-sortable-parent-path'); + data[(component_prefix + '-item_id').replace(/^-/, '')] = item_id; + if (prev_id !== null) { + data[(component_prefix + '-prev_id').replace(/^-/, '')] = prev_id; + } + if (next_id !== null) { + data[(component_prefix + '-next_id').replace(/^-/, '')] = next_id; + } + return dataGridRegisterAjaxCall({ + type: 'GET', + url: url, + data: data, + error: function(jqXHR, textStatus, errorThrown) { + return alert(jqXHR.statusText); + } + }); + }, + helper: function(e, ui) { + ui.children().each(function() { + return $(this).width($(this).width()); + }); + return ui; + } + }); +}; + +$(function() { + return datagridSortable(); +}); + +if (typeof datagridSortableTree === 'undefined') { + datagridSortableTree = function() { + if (typeof $('.datagrid-tree-item-children').sortable === 'undefined') { + return; + } + return $('.datagrid-tree-item-children').sortable({ + handle: '.handle-sort', + items: '.datagrid-tree-item:not(.datagrid-tree-header)', + toleranceElement: '> .datagrid-tree-item-content', + connectWith: '.datagrid-tree-item-children', + update: function(event, ui) { + var component_prefix, data, item_id, next_id, parent, parent_id, prev_id, row, url; + $('.toggle-tree-to-delete').remove(); + row = ui.item.closest('.datagrid-tree-item[data-id]'); + item_id = row.data('id'); + prev_id = null; + next_id = null; + parent_id = null; + if (row.prev().length) { + prev_id = row.prev().data('id'); + } + if (row.next().length) { + next_id = row.next().data('id'); + } + parent = row.parent().closest('.datagrid-tree-item'); + if (parent.length) { + parent.find('.datagrid-tree-item-children').first().css({ + display: 'block' + }); + parent.addClass('has-children'); + parent_id = parent.data('id'); + } + url = $(this).data('sortable-url'); + if (!url) { + return; + } + parent.find('[data-toggle-tree]').first().removeClass('hidden'); + component_prefix = row.closest('.datagrid-tree').attr('data-sortable-parent-path'); + data = {}; + data[(component_prefix + '-item_id').replace(/^-/, '')] = item_id; + if (prev_id !== null) { + data[(component_prefix + '-prev_id').replace(/^-/, '')] = prev_id; + } + if (next_id !== null) { + data[(component_prefix + '-next_id').replace(/^-/, '')] = next_id; + } + data[(component_prefix + '-parent_id').replace(/^-/, '')] = parent_id; + return dataGridRegisterAjaxCall({ + type: 'GET', + url: url, + data: data, + error: function(jqXHR, textStatus, errorThrown) { + if (errorThrown !== 'abort') { + return alert(jqXHR.statusText); + } + } + }); + }, + stop: function(event, ui) { + return $('.toggle-tree-to-delete').removeClass('toggle-tree-to-delete'); + }, + start: function(event, ui) { + var parent; + parent = ui.item.parent().closest('.datagrid-tree-item'); + if (parent.length) { + if (parent.find('.datagrid-tree-item').length === 2) { + return parent.find('[data-toggle-tree]').addClass('toggle-tree-to-delete'); + } + } + } + }); + }; +} + +$(function() { + return datagridSortableTree(); +}); + +dataGridRegisterExtension('datagrid.happy', { + success: function() { + var c, checked_rows, class_selector, classes, event, grid, grids, i, ie, input, j, len, len1, results; + if (window.happy) { + window.happy.reset(); + } + grids = $('.datagrid'); + results = []; + for (i = 0, len = grids.length; i < len; i++) { + grid = grids[i]; + classes = grid.classList; + class_selector = ''; + for (j = 0, len1 = classes.length; j < len1; j++) { + c = classes[j]; + class_selector = class_selector + '.' + c; + } + checked_rows = document.querySelectorAll(class_selector + ' ' + 'input[data-check]:checked'); + if (checked_rows.length === 1 && checked_rows[0].getAttribute('name') === 'toggle-all') { + input = document.querySelector(class_selector + ' input[name=toggle-all]'); + if (input) { + input.checked = false; + ie = window.navigator.userAgent.indexOf("MSIE "); + if (ie) { + event = document.createEvent('Event'); + event.initEvent('change', true, true); + } else { + event = new Event('change', { + 'bubbles': true + }); + } + results.push(input.dispatchEvent(event)); + } else { + results.push(void 0); + } + } else { + results.push(void 0); + } + } + return results; + } +}); + +dataGridRegisterExtension('datagrid.sortable', { + success: function() { + return datagridSortable(); + } +}); + +dataGridRegisterExtension('datagrid.forms', { + success: function() { + return $('.datagrid').find('form').each(function() { + return window.Nette.initForm(this); + }); + } +}); + +dataGridRegisterExtension('datagrid.url', { + success: function(payload) { + var host, path, query, url; + if (payload._datagrid_url) { + if (window.history.replaceState) { + host = window.location.protocol + "//" + window.location.host; + path = window.location.pathname; + query = window.datagridSerializeUrl(payload.state).replace(/&+$/gm, ''); + if (query) { + url = host + path + "?" + query.replace(/\&*$/, ''); + } else { + url = host + path; + } + url += window.location.hash; + if (window.location.href !== url) { + return window.history.replaceState({ + path: url + }, '', url); + } + } + } + } +}); + +dataGridRegisterExtension('datagrid.sort', { + success: function(payload) { + var href, key, ref, results; + if (payload._datagrid_sort) { + ref = payload._datagrid_sort; + results = []; + for (key in ref) { + href = ref[key]; + results.push($('#datagrid-sort-' + key).attr('href', href)); + } + return results; + } + } +}); + +dataGridRegisterExtension('datargid.item_detail', { + before: function(xhr, settings) { + var id, row_detail, grid_fullname; + if (settings.nette && settings.nette.el.attr('data-toggle-detail')) { + id = settings.nette.el.attr('data-toggle-detail'); + grid_fullname = settings.nette.el.attr('data-toggle-detail-grid-fullname'); + row_detail = $('.item-detail-' + grid_fullname + '-id-' + id); + if (row_detail.hasClass('loaded')) { + if (!row_detail.find('.item-detail-content').length) { + row_detail.removeClass('toggled'); + return true; + } + if (row_detail.hasClass('toggled')) { + row_detail.find('.item-detail-content').slideToggle('fast', (function(_this) { + return function() { + return row_detail.toggleClass('toggled'); + }; + })(this)); + } else { + row_detail.toggleClass('toggled'); + row_detail.find('.item-detail-content').slideToggle('fast'); + } + return false; + } else { + return row_detail.addClass('loaded'); + } + } + return true; + }, + success: function(payload) { + var id, row_detail, grid_fullname; + if (payload._datagrid_toggle_detail && payload._datagrid_name) { + id = payload._datagrid_toggle_detail; + grid_fullname = payload._datagrid_name; + row_detail = $('.item-detail-' + grid_fullname + '-id-' + id); + row_detail.toggleClass('toggled'); + return row_detail.find('.item-detail-content').slideToggle('fast'); + } + } +}); + +dataGridRegisterExtension('datagrid.tree', { + before: function(xhr, settings) { + var children_block; + if (settings.nette && settings.nette.el.attr('data-toggle-tree')) { + settings.nette.el.toggleClass('toggle-rotate'); + children_block = settings.nette.el.closest('.datagrid-tree-item').find('.datagrid-tree-item-children').first(); + if (children_block.hasClass('loaded')) { + children_block.slideToggle('fast'); + return false; + } + } + return true; + }, + success: function(payload) { + var children_block, content, id, name, ref, snippet, template; + if (payload._datagrid_tree) { + id = payload._datagrid_tree; + children_block = $('.datagrid-tree-item[data-id="' + id + '"]').find('.datagrid-tree-item-children').first(); + children_block.addClass('loaded'); + ref = payload.snippets; + for (name in ref) { + snippet = ref[name]; + content = $(snippet); + template = $('
'); + template.attr('data-id', content.attr('data-id')); + template.append(content); + if (content.data('has-children')) { + template.addClass('has-children'); + } + children_block.append(template); + } + children_block.addClass('loaded'); + children_block.slideToggle('fast'); + dataGridLoad(); + } + return datagridSortableTree(); + } +}); + +$(document).on('click', '[data-datagrid-editable-url]', function(event) { + var attr_name, attr_value, attrs, cell, cellValue, cell_height, cell_lines, cell_padding, input, line_height, submit, valueToEdit; + cell = $(this); + if (event.target.tagName.toLowerCase() === 'a') { + return; + } + if (cell.hasClass('datagrid-inline-edit')) { + return; + } + if (!cell.hasClass('editing')) { + cell.addClass('editing'); + cellValue = cell.html().trim().replace('
', '\n'); + if (cell.attr('data-datagrid-editable-value')) { + valueToEdit = String(cell.data('datagrid-editable-value')); + } else { + valueToEdit = cellValue; + } + cell.data('originalValue', cellValue); + cell.data('valueToEdit', valueToEdit); + if (cell.data('datagrid-editable-type') === 'textarea') { + input = $(''); + cell_padding = parseInt(cell.css('padding').replace(/[^-\d\.]/g, ''), 10); + cell_height = cell.outerHeight(); + line_height = Math.round(parseFloat(cell.css('line-height'))); + cell_lines = (cell_height - (2 * cell_padding)) / line_height; + input.attr('rows', Math.round(cell_lines)); + } else if (cell.data('datagrid-editable-type') === 'select') { + input = $(cell.data('datagrid-editable-element')); + input.find("option[value='" + valueToEdit + "']").prop('selected', true); + } else { + input = $(''); + input.val(valueToEdit); + } + attrs = cell.data('datagrid-editable-attrs'); + for (attr_name in attrs) { + attr_value = attrs[attr_name]; + input.attr(attr_name, attr_value); + } + cell.removeClass('edited'); + cell.html(input); + submit = function(cell, el) { + var value; + value = el.val(); + if (value !== cell.data('valueToEdit')) { + dataGridRegisterAjaxCall({ + url: cell.data('datagrid-editable-url'), + data: { + value: value + }, + type: 'POST', + success: function(payload) { + if (cell.data('datagrid-editable-type') === 'select') { + cell.html(input.find("option[value='" + value + "']").html()); + } else { + if (payload._datagrid_editable_new_value) { + value = payload._datagrid_editable_new_value; + } + cell.html(value); + } + return cell.addClass('edited'); + }, + error: function() { + cell.html(cell.data('originalValue')); + return cell.addClass('edited-error'); + } + }); + } else { + cell.html(cell.data('originalValue')); + } + return setTimeout(function() { + return cell.removeClass('editing'); + }, 1200); + }; + cell.find('input,textarea,select').focus().on('blur', function() { + return submit(cell, $(this)); + }).on('keydown', function(e) { + if (cell.data('datagrid-editable-type') !== 'textarea') { + if (e.which === 13) { + e.stopPropagation(); + e.preventDefault(); + return submit(cell, $(this)); + } + } + if (e.which === 27) { + e.stopPropagation(); + e.preventDefault(); + cell.removeClass('editing'); + return cell.html(cell.data('originalValue')); + } + }); + return cell.find('select').on('change', function() { + return submit(cell, $(this)); + }); + } +}); + +dataGridRegisterExtension('datagrid.after_inline_edit', { + success: function(payload) { + var grid = $('.datagrid-' + payload._datagrid_name); + + if (payload._datagrid_inline_edited) { + grid.find('tr[data-id="' + payload._datagrid_inline_edited + '"] > td').addClass('edited'); + return grid.find('.datagrid-inline-edit-trigger').removeClass('hidden'); + } else if (payload._datagrid_inline_edit_cancel) { + return grid.find('.datagrid-inline-edit-trigger').removeClass('hidden'); + } + } +}); + +$(document).on('mouseup', '[data-datagrid-cancel-inline-add]', function(e) { + var code = e.which || e.keyCode || 0; + if (code === 1) { + e.stopPropagation(); + e.preventDefault(); + return $('.datagrid-row-inline-add').addClass('datagrid-row-inline-add-hidden'); + } +}); + +dataGridRegisterExtension('datagrid-toggle-inline-add', { + success: function(payload) { + var grid = $('.datagrid-' + payload._datagrid_name); + + if (payload._datagrid_inline_adding) { + var row = grid.find('.datagrid-row-inline-add'); + + if (row.hasClass('datagrid-row-inline-add-hidden')) { + row.removeClass('datagrid-row-inline-add-hidden'); + } + + row.find('input:not([readonly]),textarea:not([readonly])').first().focus(); + } + } +}); + +datagridFitlerMultiSelect = function() { + var select = $('.selectpicker').first(); + + if ($.fn.selectpicker) { + return $.fn.selectpicker.defaults = { + countSelectedText: select.data('i18n-selected'), + iconBase: '', + tickIcon: select.data('selected-icon-check') + }; + } +}; + +$(function() { + return datagridFitlerMultiSelect(); +}); + +datagridGroupActionMultiSelect = function() { + var selects; + + if (!$.fn.selectpicker) { + return; + } + + selects = $('[data-datagrid-multiselect-id]'); + + return selects.each(function() { + var id; + if ($(this).hasClass('selectpicker')) { + $(this).removeAttr('id'); + id = $(this).data('datagrid-multiselect-id'); + $(this).on('loaded.bs.select', function(e) { + $(this).parent().attr('style', 'display:none;'); + return $(this).parent().find('.hidden').removeClass('hidden').addClass('btn-default btn-secondary'); + }); + return $(this).on('rendered.bs.select', function(e) { + return $(this).parent().attr('id', id); + }); + } + }); +}; + +$(function() { + return datagridGroupActionMultiSelect(); +}); + +dataGridRegisterExtension('datagrid.fitlerMultiSelect', { + success: function() { + datagridFitlerMultiSelect(); + if ($.fn.selectpicker) { + return $('.selectpicker').selectpicker({ + iconBase: 'fa' + }); + } + } +}); + +dataGridRegisterExtension('datagrid.groupActionMultiSelect', { + success: function() { + return datagridGroupActionMultiSelect(); + } +}); + +dataGridRegisterExtension('datagrid.inline-editing', { + success: function(payload) { + var grid; + if (payload._datagrid_inline_editing) { + grid = $('.datagrid-' + payload._datagrid_name); + return grid.find('.datagrid-inline-edit-trigger').addClass('hidden'); + } + } +}); + +dataGridRegisterExtension('datagrid.redraw-item', { + success: function(payload) { + var row; + if (payload._datagrid_redraw_item_class) { + row = $('tr[data-id="' + payload._datagrid_redraw_item_id + '"]'); + return row.attr('class', payload._datagrid_redraw_item_class); + } + } +}); + +dataGridRegisterExtension('datagrid.reset-filter-by-column', { + success: function(payload) { + var grid, href, i, key, len, ref; + if (!payload._datagrid_name) { + return; + } + grid = $('.datagrid-' + payload._datagrid_name); + grid.find('[data-datagrid-reset-filter-by-column]').addClass('hidden'); + if (payload.non_empty_filters && payload.non_empty_filters.length) { + ref = payload.non_empty_filters; + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + grid.find('[data-datagrid-reset-filter-by-column="' + key + '"]').removeClass('hidden'); + } + href = grid.find('.reset-filter').attr('href'); + return grid.find('[data-datagrid-reset-filter-by-column]').each(function() { + var new_href; + key = $(this).attr('data-datagrid-reset-filter-by-column'); + new_href = href.replace('do=' + payload._datagrid_name + '-resetFilter', 'do=' + payload._datagrid_name + '-resetColumnFilter'); + new_href += '&' + payload._datagrid_name + '-key=' + key; + return $(this).attr('href', new_href); + }); + } + } +}); + +// Expose datagrid helpers as globals for ublaboo's inline scripts. Under webpack these +// top-level `var`s were implicit globals; ESM modules are scoped, so expose explicitly. +window.datagridFitlerMultiSelect = datagridFitlerMultiSelect; +window.datagridGroupActionMultiSelect = datagridGroupActionMultiSelect; +window.datagridShiftGroupSelection = datagridShiftGroupSelection; +window.datagridSortableTree = datagridSortableTree; +window.getEventDomPath = getEventDomPath; +if (typeof datagridSortable !== 'undefined') { + window.datagridSortable = datagridSortable; +} diff --git a/assets/js/flashes.js b/assets/js/flashes.js new file mode 100644 index 0000000..263ee0e --- /dev/null +++ b/assets/js/flashes.js @@ -0,0 +1,30 @@ +import $ from 'jquery'; + +const scheduleAutoClose = (root) => { + $(root).find('.alert[data-close-duration]').each(function () { + const $alert = $(this); + if ($alert.data('auto-close-scheduled')) { + return; + } + const duration = parseInt($alert.attr('data-close-duration'), 10); + if (!duration) { + return; + } + $alert.data('auto-close-scheduled', true); + setTimeout(() => $alert.fadeOut(200, () => $alert.remove()), duration); + }); +}; + +$(document).on('click', '.alert .alert-close-btn', function () { + $(this).closest('.alert').remove(); +}); + +$(document).ready(() => scheduleAutoClose(document)); + +if (window.$ && window.$.nette) { + $.nette.ext('flashes', { + success: function () { + scheduleAutoClose(document); + } + }); +} diff --git a/assets/js/history.ajax.js b/assets/js/history.ajax.js index f16ffb7..03eab95 100644 --- a/assets/js/history.ajax.js +++ b/assets/js/history.ajax.js @@ -168,3 +168,44 @@ }); })(jQuery); + +(function ($, undefined) { + + var getScrollContainer = function () { + var $container = $('[data-app-container]'); + return $container.length ? $container.get(0) : (document.scrollingElement || document.documentElement); + }; + + var pendingScroll = null; + var inFlight = 0; + + $.nette.ext('scrollRestore', { + before: function (xhr, settings) { + if (settings.nette && settings.nette.form) { + var container = getScrollContainer(); + pendingScroll = { + top: container.scrollTop, + left: container.scrollLeft + }; + } + }, + start: function () { + inFlight++; + }, + complete: function () { + inFlight = Math.max(0, inFlight - 1); + if (inFlight === 0 && pendingScroll !== null) { + var scroll = pendingScroll; + pendingScroll = null; + var restore = function () { + var container = getScrollContainer(); + container.scrollTop = scroll.top; + container.scrollLeft = scroll.left; + }; + restore(); + window.requestAnimationFrame(restore); + } + } + }); + +})(jQuery); diff --git a/assets/js/keycloak.js b/assets/js/keycloak.js new file mode 100644 index 0000000..caec52f --- /dev/null +++ b/assets/js/keycloak.js @@ -0,0 +1,63 @@ +/** + * Keycloak session management pro přihlášené uživatele. + * + * - Inicializuje keycloak-js adapter s check-sso + * - Periodicky refreshuje token (každých 30s) + * - Při ztrátě session (expirovaný token, odhlášení z KC) odhlásí uživatele z aplikace + * + * Nastavení se čtou z window.__keycloakSettings (injektováno v @layout.latte). + * Vyžaduje npm balíček `keycloak-js` v projektu. + */ +export async function keycloakLoginSync() { + const settings = window.__keycloakSettings; + + if (!settings) { + return; + } + + delete window.__keycloakSettings; + + const TOKEN_REFRESH_INTERVAL = 30; + const TOKEN_MIN_VALIDITY = 60; + + try { + const { default: Keycloak } = await import('keycloak-js'); + + const keycloak = new Keycloak({ + url: settings.url, + realm: settings.realm, + clientId: settings.clientId + }); + + keycloak.onTokenExpired = () => { + keycloak.updateToken(TOKEN_MIN_VALIDITY).catch(() => { + window.location.href = settings.logoutUrl || '/sign/out'; + }); + }; + + keycloak.onAuthLogout = () => { + window.location.href = settings.logoutUrl || '/sign/out'; + }; + + const authenticated = await keycloak.init({ + onLoad: 'check-sso', + silentCheckSsoRedirectUri: settings.silentCheckSsoUrl, + responseMode: 'query', + checkLoginIframe: true, + checkLoginIframeInterval: 30, + }); + + if (!authenticated) { + return; + } + + setInterval(() => { + keycloak.updateToken(TOKEN_MIN_VALIDITY).catch(() => { + window.location.href = settings.logoutUrl || '/sign/out'; + }); + }, TOKEN_REFRESH_INTERVAL * 1000); + + } catch (error) { + console.error('Failed to initialize Keycloak adapter:', error); + } +} diff --git a/assets/js/sidePanel.js b/assets/js/sidePanel.js new file mode 100644 index 0000000..db30434 --- /dev/null +++ b/assets/js/sidePanel.js @@ -0,0 +1,38 @@ +$(function () { + let isDirty = false; + + function closeSidePanel() { + $('#snippet--sidePanel').html(''); + isDirty = false; + } + + function tryClose() { + const confirmMessage = $('#snippet--sidePanel .side-panel-config').data('close-confirm') || 'Close without saving?'; + if (isDirty && !confirm(confirmMessage)) return; + closeSidePanel(); + } + + // Po AJAX odpovědi: reset dirty jen při úspěšném save + // payload.hasErrors = true nastavuje BootstrapFormRenderer při validační chybě + $.nette.ext('sidePanelDirty', { + success: function (payload) { + if (payload.hasErrors) return; // validace selhala → dirty zůstane + if (payload.snippets && ('snippet--sidePanel' in payload.snippets)) { + isDirty = false; // save OK nebo panel se otevřel → čisté + } + } + }); + + // Změna hodnoty v inputu → dirty + $(document).on('change input', '#snippet--sidePanel form :input', () => { + isDirty = true; + }); + + // Přidání / odebrání řádku replikátorem → dirty + $(document).on('click', '#snippet--sidePanel [data-adt-replicator-add], #snippet--sidePanel [data-adt-replicator-remove]', () => { + isDirty = true; + }); + + $(document).on('click', '.side-panel-template-backdrop', tryClose); + $(document).on('click', '.side-panel-template-container .btn-close', tryClose); +}); \ No newline at end of file diff --git a/assets/js/signInKeycloak.js b/assets/js/signInKeycloak.js new file mode 100644 index 0000000..62c981f --- /dev/null +++ b/assets/js/signInKeycloak.js @@ -0,0 +1,81 @@ +/** + * SignInForm — Keycloak email check. + * + * Po opuštění emailového pole ověří, zda email existuje v Keycloaku. + * Pokud ano, přesměruje na Keycloak login stránku s předvyplněným emailem. + * Pokud ne, nechá formulář normálně fungovat pro password login. + * + * Aktivuje se pro každý email input s atributem `data-keycloak-check-url`. + * + * Listener je navázaný delegovaně na `document`, takže funguje i pro formulář + * vložený přes AJAX (naja snippet), aniž by se musel znovu inicializovat. + */ +const EMAIL_INPUT_ID = 'login-form-input-email'; +const PASSWORD_INPUT_ID = 'login-form-input-password'; + +let bound = false; + +async function handleEmailChange(event) { + const emailInput = event.target; + + if (!(emailInput instanceof HTMLElement) || emailInput.id !== EMAIL_INPUT_ID) { + return; + } + + const checkUrl = emailInput.getAttribute('data-keycloak-check-url'); + if (!checkUrl) { + return; + } + + const email = emailInput.value.trim(); + if (!email || emailInput.dataset.keycloakChecking === '1') { + return; + } + + const form = emailInput.closest('form'); + const passwordInput = document.getElementById(PASSWORD_INPUT_ID); + const submitButton = form ? form.querySelector('[type="submit"]') : null; + + // Disable form while checking + emailInput.dataset.keycloakChecking = '1'; + emailInput.readOnly = true; + if (passwordInput) passwordInput.readOnly = true; + if (submitButton) submitButton.disabled = true; + + try { + const url = checkUrl.replace('__EMAIL__', encodeURIComponent(email)); + const response = await fetch(url); + const data = await response.json(); + + if (data.loginUrl) { + // User exists in Keycloak → redirect to KC login + window.location.href = data.loginUrl; + return; + } + } catch (e) { + // Fetch failed, fall through to normal login + } + + // Re-enable form for normal password login + emailInput.readOnly = false; + if (passwordInput) { + passwordInput.readOnly = false; + passwordInput.focus(); + } + if (submitButton) submitButton.disabled = false; + emailInput.dataset.keycloakChecking = ''; +} + +const run = () => { + if (bound) { + return; + } + bound = true; + document.addEventListener('change', handleEmailChange); +}; + +// Naváže listener hned při importu modulu, aby fungoval i bez explicitního volání run() +// (např. když je formulář na stránce vložen až AJAXem po prvotním načtení). +run(); + +export default { run }; diff --git a/assets/js/summernote-file.js b/assets/js/summernote-file.js new file mode 100644 index 0000000..dbd9c29 --- /dev/null +++ b/assets/js/summernote-file.js @@ -0,0 +1,334 @@ +/** + * Copyright 2019 [nobsod | Mathieu Coingt]. + * Website: https://www.nobsod.fr + * Email: dev@nobsod.fr + * License: MIT + * + * Fork from summernote-audio : https://github.com/taalendigitaal/summernote-audio + */ +(function (factory) { + /* Global define */ + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = factory(require('jquery')); + } else { + // Browser globals + factory(window.jQuery); + } +}(function ($) { + $.extend(true, $.summernote.lang, { + 'en-US': { + file: { + file: 'File', + btn: 'File', + insert: 'Insert File', + selectFromFiles: 'Select from files', + url: 'File URL', + maximumFileSize: 'Maximum file size', + maximumFileSizeError: 'Maximum file size exceeded.' + } + }, + 'fr-FR': { + file: { + file: 'Fichier', + btn: 'Fichier', + insert: 'Insérer un fichier', + selectFromFiles: 'Sélectionner depuis les fichiers', + url: 'URL du fichier', + maximumFileSize: 'Poids maximum du fichier', + maximumFileSizeError: 'Poids maximum dépassé.' + } + }, + 'cs-CZ': { + file: { + file: 'Soubor', + btn: 'Soubor', + insert: 'Vložit soubor', + selectFromFiles: 'Vybrat ze souborů', + url: 'URL souboru', + maximumFileSize: 'Maximální velikost souboru', + maximumFileSizeError: 'Maximální velikost souboru překročena.' + } + }, + }); + + $.extend($.summernote.options, { + file: { + icon: '' + }, + callbacks: { + onFileUpload: null, + onFileUploadError: null, + onFileLinkInsert: null + } + }); + + $.extend($.summernote.plugins, { + /** + * @param {Object} context - context object has status of editor. + */ + 'file': function (context) { + let self = this, + // ui has renders to build ui elements + // for e.g. you can create a button with 'ui.button' + ui = $.summernote.ui, + $note = context.layoutInfo.note, + // contentEditable element + $editor = context.layoutInfo.editor, + $editable = context.layoutInfo.editable, + $toolbar = context.layoutInfo.toolbar, + // options holds the Options Information from Summernote and what we extended above. + options = context.options, + // lang holds the Language Information from Summernote and what we extended above. + lang = options.langInfo; + + context.memo('button.file', function () { + // Here we create a button + let button = ui.button({ + // icon for button + contents: options.file.icon, + + // tooltip for button + tooltip: lang.file.file, + click: function (e) { + e.stopPropagation(); + context.invoke('file.show'); + } + }); + return button.render(); + }); + + this.initialize = function () { + // This is how we can add a Modal Dialog to allow users to interact with the Plugin. + + // get the correct container for the plugin how it's attached to the document DOM. + let $container = options.dialogsInBody ? $(document.body) : $editor; + + let fileLimitation = ''; + if (options.maximumFileSize) { + let unit = Math.floor(Math.log(options.maximumFileSize) / Math.log(1024)); + let readableSize = (options.maximumFileSize / Math.pow(1024, unit)).toFixed(2) * 1 + + ' ' + ' KMGTP'[unit] + 'B'; + fileLimitation = '' + lang.file.maximumFileSize + ' : ' + readableSize + ''; + } + + // Build the Body HTML of the Dialog. + let body = [ + '
', + '', + '', + '
', + fileLimitation, + '
', + '', + '', + '
' + ].join(''); + + // Build the Footer HTML of the Dialog. + let footer = ''; + + this.$dialog = ui.dialog({ + + // Set the title for the Dialog. Note: We don't need to build the markup for the Modal + // Header, we only need to set the Title. + title: lang.file.insert, + + // Set the Body of the Dialog. + body: body, + + // Set the Footer of the Dialog. + footer: footer + + // This adds the Modal to the DOM. + }).render().appendTo($container); + }; + + this.destroy = function () { + ui.hideDialog(this.$dialog); + this.$dialog.remove(); + }; + + this.bindEnterKey = function ($input, $btn) { + $input.on('keypress', function (event) { + if (event.keyCode === 13) + $btn.trigger('click'); + }); + }; + + this.bindLabels = function () { + self.$dialog.find('.form-control:first').focus().select(); + self.$dialog.find('label').on('click', function () { + $(this).parent().find('.form-control:first').focus(); + }); + }; + + /** + * @method readFileAsDataURL + * + * read contents of file as representing URL + * + * @param {File} file + * @return {Promise} - then: dataUrl + * + * @todo this method already exists in summernote.js so we should use that one + */ + this.readFileAsDataURL = function (file) { + return $.Deferred(function (deferred) { + $.extend(new FileReader(), { + onload: function (e) { + let dataURL = e.target.result; + deferred.resolve(dataURL); + }, + onerror: function (err) { + deferred.reject(err); + } + }).readAsDataURL(file); + }).promise(); + }; + + this.createFile = function (url) { + // IMG url patterns (jpg, jpeg, png, gif, svg, webp) + let imgRegExp = /^.+.(jpg|jpeg|png|gif|svg|webp)$/; + let imgBase64RegExp = /^data:(image\/jpeg|image\/png|image\/gif|image\/svg|image\/webp).+$/; + + // AUDIO url patterns (mp3, ogg, oga) + let audioRegExp = /^.+.(mp3|ogg|oga)$/; + let audioBase64RegExp = /^data:(audio\/mpeg|audio\/ogg).+$/; + + // VIDEO url patterns (mp4, ogc, webm) + let videoRegExp = /^.+.(mp4|ogv|webm)$/; + let videoBase64RegExp = /^data:(video\/mpeg|video\/mp4|video\/ogv|video\/webm).+$/; + + let $file; + if (url.match(imgRegExp) || url.match(imgBase64RegExp)) { + $file = $('') + .attr('src', url) + ; + } else if (url.match(audioRegExp) || url.match(audioBase64RegExp)) { + $file = $('