Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 6 additions & 3 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,19 @@ jobs:
const pr = prs.data.find(p => p.merged_at);
const fs = require('fs');
if (pr) {
fs.writeFileSync('release_notes.md', pr.body || 'No release notes provided.');
const body = (pr.body || '')
.replace(/##\s*Summary by CodeRabbit[\s\S]*/i, '')
.trim() || 'No release notes provided.';
fs.writeFileSync('release_notes.md', body);
} else {
fs.writeFileSync('release_notes.md', 'No release notes provided.');
}

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: Release v${{ steps.version.outputs.version }}
tag_name: ${{ steps.version.outputs.version }}
name: ${{ steps.version.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: false
Expand Down
28 changes: 28 additions & 0 deletions Core/Resgrid.Config/CalendarConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Resgrid.Config
{
/// <summary>
/// Configuration settings for the Calendar feature, including iCal feed export and external sync.
/// Values can be overridden via ResgridConfig.json using keys like "CalendarConfig.ICalFeedEnabled".
/// </summary>
public static class CalendarConfig
{
/// <summary>
/// Feature flag to enable or disable the public unauthenticated iCal feed endpoint.
/// Set to false to prevent external calendar applications from subscribing.
/// </summary>
public static bool ICalFeedEnabled = true;

/// <summary>
/// The PRODID value written into generated iCal (RFC 5545) files.
/// Format: -//Company//Product//Language
/// </summary>
public static string ICalProductId = "-//Resgrid//Calendar//EN";

/// <summary>
/// How long (in minutes) a subscribing calendar client may cache the feed response.
/// This value is written into the X-WR-CACHETIME header on feed responses.
/// </summary>
public static int ICalFeedCacheDurationMinutes = 15;
}
}

97 changes: 97 additions & 0 deletions Core/Resgrid.Config/SsoConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

#pragma warning disable S2223 // Non-constant static fields should not be visible
#pragma warning disable CA2211 // Non-constant fields should not be visible
#pragma warning disable S1104 // Fields should not have public accessibility

namespace Resgrid.Config
{
/// <summary>
/// System-wide configuration for Single Sign-On (SSO), SAML 2.0, OIDC, and SCIM 2.0.
/// All runtime URL paths and protocol-level constants used by the SSO/SCIM feature
/// should be defined here rather than embedded inside services or providers.
/// </summary>
public static class SsoConfig
{
// ── SCIM 2.0 ─────────────────────────────────────────────────────────

/// <summary>
/// Relative path segment appended to <see cref="SystemBehaviorConfig.ResgridApiBaseUrl"/>
/// to form the SCIM 2.0 connector base URL presented to identity providers.
/// Example result: https://api.resgrid.com/scim/v2
/// </summary>
public static string ScimBasePath = "/scim/v2";

/// <summary>
/// Number of cryptographically random bytes used when generating a new SCIM bearer token.
/// The resulting Base64-encoded token will be (ScimBearerTokenByteLength * 4/3) characters long.
/// Default: 48 bytes → 64-character Base64 token.
/// </summary>
public static int ScimBearerTokenByteLength = 48;

/// <summary>
/// Compile-time constant for the HTTP header name that SCIM clients must include
/// to identify the target department. Used in [FromHeader(Name = ...)] attributes.
/// If you need to change this value, also update <see cref="ScimDepartmentIdHeaderName"/>.
/// </summary>
public const string ScimDepartmentIdHeader = "X-Department-Id";

/// <summary>
/// Runtime-configurable HTTP header name that SCIM clients must include to identify
/// the target department when the bearer token alone is insufficient for routing
/// (e.g. Microsoft Entra ID). Defaults to <see cref="ScimDepartmentIdHeader"/>.
/// </summary>
public static string ScimDepartmentIdHeaderName = ScimDepartmentIdHeader;

// ── SSO / OIDC ────────────────────────────────────────────────────────

/// <summary>
/// Relative URL path for the SSO discovery endpoint that mobile apps call to
/// retrieve department SSO settings before showing the login screen.
/// A <c>departmentToken</c> query parameter is appended at runtime.
/// Example result: https://api.resgrid.com/api/v4/connect/sso-config
/// </summary>
public static string SsoDiscoveryPath = "/api/v4/connect/sso-config";

/// <summary>
/// Relative URL path for the SAML 2.0 Assertion Consumer Service (ACS) used
/// by mobile clients. A <c>departmentToken</c> query parameter is appended at runtime.
/// Example result: https://api.resgrid.com/api/v4/connect/saml-mobile-callback
/// </summary>
public static string SamlAcsPath = "/api/v4/connect/saml-mobile-callback";

/// <summary>
/// Relative URL path segment used to construct SAML SP Entity IDs.
/// Example result: https://api.resgrid.com/saml/{configId}
/// </summary>
public static string SamlEntityIdBasePath = "/saml/";

// ── Feature flags ─────────────────────────────────────────────────────

/// <summary>
/// When <c>true</c>, the SSO / SCIM feature is available for departments to configure.
/// When <c>false</c>, the SSO / SCIM navigation entries and controllers are hidden
/// system-wide regardless of department configuration.
/// </summary>
public static bool SsoFeatureEnabled = true;

/// <summary>
/// When <c>true</c>, SCIM 2.0 provisioning endpoints are enabled system-wide.
/// When <c>false</c>, SCIM endpoints return HTTP 503 regardless of department config.
/// </summary>
public static bool ScimFeatureEnabled = true;

/// <summary>
/// When <c>true</c>, the system enforces IP-range restrictions from
/// DepartmentSecurityPolicy.AllowedIpRanges on every login attempt.
/// Disable in development environments where NAT/proxy addresses are unpredictable.
/// </summary>
public static bool IpRangeEnforcementEnabled = true;
}
}

#pragma warning restore CA2211 // Non-constant fields should not be visible
#pragma warning restore S2223 // Non-constant static fields should not be visible
#pragma warning restore S1104 // Fields should not have public accessibility



3 changes: 3 additions & 0 deletions Core/Resgrid.Config/WorkflowConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public static class WorkflowConfig
/// <summary>Maximum length in characters of rendered template content passed to an executor (256 KB).</summary>
public static int MaxRenderedContentLength = 262144;

/// <summary>Maximum length in characters for a ConditionExpression on a WorkflowStep (4 KB).</summary>
public static int MaxConditionExpressionLength = 4096;

// ── Scriban Sandbox Limits ───────────────────────────────────────────────────

/// <summary>Maximum number of loop iterations in a Scriban template.</summary>
Expand Down
206 changes: 206 additions & 0 deletions Core/Resgrid.Localization/Account/Login.de.resx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>

<data name="Name1" xml:space="preserve">
<value>this is my long string</value>
</data>
<data name="Bitmap1" xml:space="preserve">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" xml:space="preserve">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
</data>
<data name="CompletedInviteHeader" xml:space="preserve">
<value>Einladung abgeschlossen</value>
</data>
<data name="CompleteInviteHeader" xml:space="preserve">
<value>Einladung abschließen</value>
</data>
<data name="CompleteInviteText1" xml:space="preserve">
<value>Supply the information to create your own Resgrid account and join</value>
</data>
<data name="CompleteInviteText2" xml:space="preserve">
<value>The information you are supplying is used to create your account in Resgrid. If you already have an account your existing account will be linked with the department as part of this invite.</value>
</data>
<data name="CompleteInviteText3" xml:space="preserve">
<value>If you are having trouble completing the invite or are running into issues, please reach out to the Administrator of the department for the invite you are trying to complete for assistance. They can manually add your account if necessary.</value>
</data>
<data name="ConfirmPassword" xml:space="preserve">
<value>Passwort bestätigen</value>
</data>
<data name="CreateAccount" xml:space="preserve">
<value>Konto erstellen</value>
</data>
<data name="DepartmentName" xml:space="preserve">
<value>Abteilungsname</value>
</data>
<data name="DontHaveAccount" xml:space="preserve">
<value>Kein Konto?</value>
</data>
<data name="EmailAddress" xml:space="preserve">
<value>E-Mail-Adresse</value>
</data>
<data name="FirstName" xml:space="preserve">
<value>Vorname</value>
</data>
<data name="ForgotPassword" xml:space="preserve">
<value>Benutzername oder Passwort vergessen?</value>
</data>
<data name="GoBack" xml:space="preserve">
<value>Zurück</value>
</data>
<data name="Home" xml:space="preserve">
<value>Startseite</value>
</data>
<data name="InviteCompletedInfo" xml:space="preserve">
<value>It looks this invite has already been completed. If this is in error contact your department or origination to get another invite, else you can log in with your username or password.</value>
</data>
<data name="InviteCompletedNotice" xml:space="preserve">
<value>Resgrid Invite has already been completed</value>
</data>
<data name="LastName" xml:space="preserve">
<value>Nachname</value>
</data>
<data name="LockedOutHeader" xml:space="preserve">
<value>Gesperrt</value>
</data>
<data name="LockedOutText" xml:space="preserve">
<value>This account has been locked out, please try and log in again in 30 minutes. Please note support cannot unlock an account for you.</value>
</data>
<data name="LogIn" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="LoginMessage1" xml:space="preserve">
<value>Enter your username and password to login.</value>
</data>
<data name="LoginMessage2" xml:space="preserve">
<value>If your department has already signed up ask that departments admin for an invite.</value>
</data>
<data name="LoginMessage3" xml:space="preserve">
<value>If your department has not yet signed up for Resgrid you can create a new account which will create the department for you.</value>
</data>
<data name="LoginMessage4" xml:space="preserve">
<value>Maintenance is performed weekly on Saturday starting at 2000 hours Pacific.</value>
</data>
<data name="LogOnHeader" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="MissingInviteBody" xml:space="preserve">
<value>We're sorry but we were unable to locate your invite to Resgrid. Try having your department or organization send another one. If you already have a Resgrid account you can log in below.</value>
</data>
<data name="MissingInviteHeader" xml:space="preserve">
<value>Invite Missing</value>
</data>
<data name="MissingInviteTopText" xml:space="preserve">
<value>Unable to find your Resgrid Invite</value>
</data>
<data name="Notice" xml:space="preserve">
<value>Hinweis</value>
</data>
<data name="Password" xml:space="preserve">
<value>Passwort</value>
</data>
<data name="PasswordHelp" xml:space="preserve">
<value>Passwords must be 8 characters or longer and include a digit (number), an uppercase and lowercase letter</value>
</data>
<data name="PrivacyPolicy" xml:space="preserve">
<value>privacy policy</value>
</data>
<data name="RecoverAccountSuccess" xml:space="preserve">
<value>If your email address is valid we have reset your password and sent your username and new password to the email address supplied. Email delivery can be delayed up to 15 minutes, &lt;b&gt;also check your spam or junk areas in your email client if you email doesn't arrive&lt;/b&gt; by then.</value>
</data>
<data name="RecoverAccountText" xml:space="preserve">
<value>Supply the email address you used with your Resgrid account. We will then send you an email to that address with your username and new password to log into the system.</value>
</data>
<data name="RecoveryAccountHeader" xml:space="preserve">
<value>Konto wiederherstellen</value>
</data>
<data name="RegisterHeader" xml:space="preserve">
<value>Registrieren</value>
</data>
<data name="RegisterText1" xml:space="preserve">
<value>Registering for Resgrid is as easy as 1,2,3!</value>
</data>
<data name="RegisterText2" xml:space="preserve">
<value>Have one person from your org fill out this form</value>
</data>
<data name="RegisterText3" xml:space="preserve">
<value>Once Signed up, you can invite or add people from the Personnel page inside the app</value>
</data>
<data name="RegisterText4" xml:space="preserve">
<value>Thats it! Only a single person needs to Register the organization</value>
</data>
<data name="RegisterText5" xml:space="preserve">
<value>If someone in your organization &lt;b&gt;already has an account with Resgrid&lt;/b&gt; have them invite you to their department instead of creating a new one, if you want to use Resgrid with them.</value>
</data>
<data name="Resgister" xml:space="preserve">
<value>Resgister</value>
</data>
<data name="SignUpTerms1" xml:space="preserve">
<value>By signing up, you accept</value>
</data>
<data name="TermsOfUse" xml:space="preserve">
<value>terms of use</value>
</data>
<data name="Username" xml:space="preserve">
<value>Benutzername</value>
</data>
</root>
Loading
Loading