Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bf7ba36
Build a license map once
rvdwegen Apr 13, 2026
69172aa
Normalize API function description handling
Zacgoose Apr 14, 2026
eab9c05
Merge pull request #5687 from Zacgoose/cippcore-optimisation
KelvinTegelaar Apr 15, 2026
3b7ad08
chore(deps): bump react-virtuoso from 4.18.3 to 4.18.5
dependabot[bot] Apr 15, 2026
e3529a6
chore(deps): bump @tiptap/react from 3.20.4 to 3.20.5
dependabot[bot] Apr 15, 2026
9f68bf0
chore(deps): bump axios from 1.14.0 to 1.15.0
dependabot[bot] Apr 15, 2026
1718363
chore(deps): bump @mui/x-date-pickers from 8.27.2 to 9.0.2
dependabot[bot] Apr 15, 2026
9ce58d6
chore(deps): bump actions/github-script from 8 to 9
dependabot[bot] Apr 15, 2026
aa62533
fixes frontend loop if 500 is outside of app bounds
KelvinTegelaar Apr 16, 2026
c4cba3a
fixes frontend loop if 500 is outside of app bounds
KelvinTegelaar Apr 16, 2026
1760b59
feat: add AI Administrator role to GDAPRoles
zenturash Apr 16, 2026
3749ba7
Merge pull request #5871 from zenturash/patch-2
KelvinTegelaar Apr 16, 2026
8cd33c8
Add custom scripts to backup
Zacgoose Apr 17, 2026
6bf74e7
Merge pull request #5869 from KelvinTegelaar/dependabot/github_action…
KelvinTegelaar Apr 17, 2026
1654c02
Merge pull request #5868 from KelvinTegelaar/dependabot/npm_and_yarn/…
KelvinTegelaar Apr 17, 2026
3cb0a02
Merge pull request #5867 from KelvinTegelaar/dependabot/npm_and_yarn/…
KelvinTegelaar Apr 17, 2026
0ff7598
Merge pull request #5866 from KelvinTegelaar/dependabot/npm_and_yarn/…
KelvinTegelaar Apr 17, 2026
e7c5070
Merge pull request #5865 from KelvinTegelaar/dependabot/npm_and_yarn/…
KelvinTegelaar Apr 17, 2026
37a54d4
chore(deps): bump @mui/system from 7.3.2 to 7.3.10
dependabot[bot] Apr 17, 2026
6da156f
Endpoint changes
Zacgoose Apr 17, 2026
413cdd2
Merge pull request #5864 from KelvinTegelaar/dependabot/npm_and_yarn/…
KelvinTegelaar Apr 18, 2026
007ab71
chore(deps): update/downgrade @mui packages to version 7.3.10
kris6673 Apr 19, 2026
4ab8b4c
Merge pull request #5886 from kris6673/mui-versions
KelvinTegelaar Apr 19, 2026
4520067
change domain validation to only 1 domain segment
Zacgoose Apr 20, 2026
52c8f87
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP into dev
rvdwegen Apr 20, 2026
5527572
fixes launching emulator
Zacgoose Apr 20, 2026
0bf17bd
Update Start-CippDevEmulators.ps1
Zacgoose Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/auto_comments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
# 1) If the comment includes '!notasponsor', delete it using GitHub Script
- name: Delete !notasponsor comment
if: contains(github.event.comment.body, '!notasponsor')
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
github.event.pull_request.head.repo.fork == true &&
((github.event.pull_request.head.ref == 'main' || github.event.pull_request.head.ref == 'master') ||
(github.event.pull_request.base.ref == 'main' || github.event.pull_request.base.ref == 'master'))
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
Expand Down
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
"cwd": "${cwd}",
"script": ". '${cwd}\\Tools\\Start-CIPPDevEmulators.ps1'"
},
{
"type": "PowerShell",
"name": "Launch in Windows Terminal with Offloading Proc and HTTP only workers",
"request": "launch",
"cwd": "${cwd}",
"script": ". '${cwd}\\Tools\\Start-CippOffloadSimulation.ps1'"
},
{
"type": "PowerShell",
"name": "Launch in Kitty Terminal",
Expand Down
10 changes: 8 additions & 2 deletions Tools/Start-CippDevEmulators.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ $apiCommand = @'
try {
# Use a stable local identity so timer node selection treats this as the catch-all host.
$env:WEBSITE_SITE_NAME = "cipp"
$env:CIPP_PROCESSOR = "true"
$env:CIPP_PROCESSOR = "false"
$env:AzureFunctionsWebHost__hostid = "cipp-single"

# Ensure prior offload simulation env overrides do not disable triggers in this shell.
Expand All @@ -42,5 +42,11 @@ try {
$frontendCommand = 'try { npm run dev } catch { Write-Error $_.Exception.Message } finally { Read-Host "Press Enter to exit" }'
$swaCommand = 'try { npm run start-swa } catch { Write-Error $_.Exception.Message } finally { Read-Host "Press Enter to exit" }'

# Encode commands to avoid parsing issues with multi-line strings
$azuriteEncoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($azuriteCommand))
$apiEncoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($apiCommand))
$frontendEncoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($frontendCommand))
$swaEncoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($swaCommand))

# Start Windows Terminal with all tabs
wt --title CIPP`; new-tab --title 'Azurite' -d $Path pwsh -c $azuriteCommand`; new-tab --title 'FunctionApp' -d $ApiPath pwsh -c $apiCommand`; new-tab --title 'CIPP Frontend' -d $FrontendPath pwsh -c $frontendCommand`; new-tab --title 'SWA' -d $FrontendPath pwsh -c $swaCommand
wt --title CIPP`; new-tab --title 'Azurite' -d $Path pwsh -EncodedCommand $azuriteEncoded`; new-tab --title 'FunctionApp' -d $ApiPath pwsh -EncodedCommand $apiEncoded`; new-tab --title 'CIPP Frontend' -d $FrontendPath pwsh -EncodedCommand $frontendEncoded`; new-tab --title 'SWA' -d $FrontendPath pwsh -EncodedCommand $swaEncoded
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
"@emotion/styled": "11.14.1",
"@heroicons/react": "2.2.0",
"@monaco-editor/react": "^4.6.0",
"@mui/icons-material": "7.3.7",
"@mui/icons-material": "7.3.10",
"@mui/lab": "7.0.0-beta.17",
"@mui/material": "7.3.7",
"@mui/system": "7.3.2",
"@mui/x-date-pickers": "^8.27.2",
"@mui/material": "7.3.10",
"@mui/system": "7.3.10",
"@mui/x-date-pickers": "^9.0.2",
"@musement/iso-duration": "^1.0.0",
"@nivo/core": "^0.99.0",
"@nivo/sankey": "^0.99.0",
Expand All @@ -51,12 +51,12 @@
"@tiptap/extension-image": "^3.20.5",
"@tiptap/extension-table": "^3.19.0",
"@tiptap/pm": "^3.22.3",
"@tiptap/react": "^3.4.1",
"@tiptap/react": "^3.20.5",
"@tiptap/starter-kit": "^3.20.5",
"@uiw/react-json-view": "^2.0.0-alpha.41",
"@vvo/tzdb": "^6.198.0",
"apexcharts": "5.10.4",
"axios": "1.14.0",
"axios": "1.15.0",
"date-fns": "4.1.0",
"diff": "^8.0.3",
"eml-parse-js": "^1.2.0-beta.0",
Expand Down Expand Up @@ -100,7 +100,7 @@
"react-redux": "9.2.0",
"react-syntax-highlighter": "^16.1.0",
"react-time-ago": "^7.3.3",
"react-virtuoso": "^4.18.3",
"react-virtuoso": "^4.18.5",
"react-window": "^2.2.7",
"recharts": "^3.7.0",
"redux": "5.0.1",
Expand Down
145 changes: 127 additions & 18 deletions src/components/CippComponents/AppApprovalTemplateForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,76 @@ const AppApprovalTemplateForm = ({
refetchKey,
hideSubmitButton = false, // New prop to hide the submit button when used in a drawer
}) => {
const forbiddenManifestProperties = ["keyCredentials", "passwordCredentials"];
const [selectedPermissionSet, setSelectedPermissionSet] = useState(null);
const [permissionsLoaded, setPermissionsLoaded] = useState(false);
const [permissionSetDrawerVisible, setPermissionSetDrawerVisible] = useState(false);
const [manifestSanitizeMessage, setManifestSanitizeMessage] = useState(null);

const getManifestValidationError = (manifest) => {
if (!manifest.displayName) {
return "Application manifest must include a 'displayName' property";
}

if (manifest.signInAudience && manifest.signInAudience !== "AzureADMyOrg") {
return "signInAudience must be null, undefined, or 'AzureADMyOrg' for security reasons";
}

const presentForbiddenProperties = forbiddenManifestProperties.filter(
(propertyName) => Object.prototype.hasOwnProperty.call(manifest, propertyName)
);
if (presentForbiddenProperties.length > 0) {
return `Remove unsupported manifest properties: ${presentForbiddenProperties.join(", ")}.`;
}

return null;
};

const handleSanitizeManifest = () => {
const currentManifest = formControl.getValues("applicationManifest");

if (!currentManifest) {
setManifestSanitizeMessage({
severity: "warning",
text: "Paste a manifest first, then use cleanup.",
});
return;
}

try {
const parsedManifest = JSON.parse(currentManifest);
const removedProperties = forbiddenManifestProperties.filter((propertyName) =>
Object.prototype.hasOwnProperty.call(parsedManifest, propertyName)
);

if (removedProperties.length === 0) {
setManifestSanitizeMessage({
severity: "info",
text: "No forbidden sections found. Your manifest is already clean.",
});
return;
}

removedProperties.forEach((propertyName) => {
delete parsedManifest[propertyName];
});

formControl.setValue("applicationManifest", JSON.stringify(parsedManifest, null, 2), {
shouldDirty: true,
shouldValidate: true,
});

setManifestSanitizeMessage({
severity: "success",
text: `Removed forbidden sections: ${removedProperties.join(", ")}.`,
});
} catch (error) {
setManifestSanitizeMessage({
severity: "error",
text: "Manifest JSON is invalid. Fix the JSON and try cleanup again.",
});
}
};

// Watch for app type selection changes
const selectedAppType = useWatch({
Expand All @@ -40,6 +107,28 @@ const AppApprovalTemplateForm = ({
name: "applicationManifest",
});

const getForbiddenManifestPropertiesPresent = (manifestValue) => {
if (!manifestValue) {
return [];
}

try {
const manifest = JSON.parse(manifestValue);
return forbiddenManifestProperties.filter((propertyName) =>
Object.prototype.hasOwnProperty.call(manifest, propertyName)
);
} catch {
return [];
}
};

const forbiddenPropertiesInCurrentManifest =
selectedAppType === "ApplicationManifest"
? getForbiddenManifestPropertiesPresent(selectedApplicationManifest)
: [];
const showSanitizeManifestButton = forbiddenPropertiesInCurrentManifest.length > 0;
const isTemplateFormValid = formControl?.formState?.isValid ?? false;

// Watch for app selection changes to update template name
const selectedApp = useWatch({
control: formControl?.control,
Expand Down Expand Up @@ -236,6 +325,22 @@ const AppApprovalTemplateForm = ({
}
}, [isEditing, isCopy, templateData]);

useEffect(() => {
if (!formControl) {
return;
}

formControl.trigger();
}, [
formControl,
selectedAppType,
selectedApplicationManifest,
selectedApp,
selectedGalleryTemplate,
selectedPermissionSetValue,
templateData,
]);

// Handle form submission
const handleSubmit = (data) => {
let appDisplayName, appId, galleryTemplateId, applicationManifest;
Expand All @@ -249,11 +354,12 @@ const AppApprovalTemplateForm = ({
try {
applicationManifest = JSON.parse(data.applicationManifest);

// Validate signInAudience - only allow null/undefined or "AzureADMyOrg"
if (
applicationManifest.signInAudience &&
applicationManifest.signInAudience !== "AzureADMyOrg"
) {
const manifestValidationError = getManifestValidationError(applicationManifest);
if (manifestValidationError) {
setManifestSanitizeMessage({
severity: "error",
text: manifestValidationError,
});
return; // Don't submit if validation fails
}

Expand Down Expand Up @@ -481,24 +587,27 @@ const AppApprovalTemplateForm = ({
validate: (value) => {
try {
const manifest = JSON.parse(value);

// Check for minimum required property
if (!manifest.displayName) {
return "Application manifest must include a 'displayName' property";
}

// Validate signInAudience if present
if (manifest.signInAudience && manifest.signInAudience !== "AzureADMyOrg") {
return "signInAudience must be null, undefined, or 'AzureADMyOrg' for security reasons";
}

return true;
return getManifestValidationError(manifest) ?? true;
} catch (e) {
return "Invalid JSON format";
}
},
}}
/>
<Stack spacing={1} sx={{ mt: 1 }}>
{showSanitizeManifestButton && (
<Box>
<Button variant="outlined" onClick={handleSanitizeManifest}>
Remove Forbidden Sections
</Button>
</Box>
)}
{manifestSanitizeMessage && (
<Alert severity={manifestSanitizeMessage.severity}>
{manifestSanitizeMessage.text}
</Alert>
)}
</Stack>
</CippFormCondition>

<CippFormCondition
Expand Down Expand Up @@ -547,7 +656,7 @@ const AppApprovalTemplateForm = ({
variant="contained"
color="primary"
onClick={formControl.handleSubmit(handleSubmit)}
disabled={updatePermissions.isPending}
disabled={updatePermissions.isPending || !isTemplateFormValid}
>
{isEditing ? "Update Template" : "Create Template"}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const CippAppApprovalTemplateDrawer = ({
variant="contained"
color="primary"
onClick={formControl.handleSubmit(handleSubmit)}
disabled={updatePermissions.isPending}
disabled={updatePermissions.isPending || !formControl.formState.isValid}
>
{updatePermissions.isPending
? isEditMode
Expand Down
1 change: 1 addition & 0 deletions src/components/CippComponents/CippRestoreWizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const TABLE_LABELS = {
AppPermissions: "App Permissions",
CommunityRepos: "Community Repositories",
Config: "CIPP Configuration",
CustomPowershellScripts: "Custom PowerShell/Test Scripts",
CustomData: "Custom Data",
CustomRoles: "Custom Roles",
Domains: "Domains",
Expand Down
13 changes: 12 additions & 1 deletion src/components/CippSettings/CippPermissionCheck.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ const CippPermissionCheck = (props) => {
);
};

const responseData = executeCheck?.error?.response?.data;
const responseText =
typeof responseData === "string" ? responseData : responseData ? JSON.stringify(responseData) : "";
const shouldShowApiResponse = responseText.includes(
"Access to this CIPP API endpoint is not allowed",
);
const checkErrorMessage =
shouldShowApiResponse
? responseText
: `Failed to load ${type} check. Please try refreshing the page.`;

return (
<>
<CippButtonCard
Expand Down Expand Up @@ -141,7 +152,7 @@ const CippPermissionCheck = (props) => {
>
{executeCheck.isError && !importReport && (
<Alert severity="error" sx={{ mb: 2 }}>
Failed to load {type} check. Please try refreshing the page.
{checkErrorMessage}
</Alert>
)}
{(executeCheck.isSuccess || executeCheck.isLoading) && (
Expand Down
24 changes: 22 additions & 2 deletions src/components/CippSettings/CippRoleAddEdit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@ export const CippRoleAddEdit = ({ selectedRole }) => {
return regex.test(value);
};

const getFunctionDescriptionText = (description) => {
if (!description) return null;

if (Array.isArray(description)) {
return description?.[0]?.Text || description?.[0]?.text || null;
}

if (typeof description === "string") {
return description;
}

if (typeof description === "object") {
return description?.Text || description?.text || null;
}

return null;
};

const getBaseRolePermissions = (role) => {
const roleConfig = cippRoles[role];
if (!roleConfig) return {};
Expand Down Expand Up @@ -434,7 +452,7 @@ export const CippRoleAddEdit = ({ selectedRole }) => {
const apiFunction = apiPermissions[cat][obj][type][api];
items.push({
name: apiFunction.Name,
description: apiFunction.Description?.[0]?.Text || null,
description: getFunctionDescriptionText(apiFunction.Description),
});
}
return (
Expand Down Expand Up @@ -593,7 +611,9 @@ export const CippRoleAddEdit = ({ selectedRole }) => {
Object.keys(apiPermissions[cat][obj][type]).forEach(
(apiKey) => {
const apiFunction = apiPermissions[cat][obj][type][apiKey];
const descriptionText = apiFunction.Description?.[0]?.Text;
const descriptionText = getFunctionDescriptionText(
apiFunction.Description
);
allEndpoints.push({
label: descriptionText
? `${apiFunction.Name} - ${descriptionText}`
Expand Down
6 changes: 1 addition & 5 deletions src/components/CippWizard/CippGDAPTenantOnboarding.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,7 @@ export const CippGDAPTenantOnboarding = (props) => {
});

const relationshipList = ApiGetCall({
url: "/api/ListGraphRequest",
data: {
TenantFilter: "",
Endpoint: "tenantRelationships/delegatedAdminRelationships",
},
url: "/api/ListGDAPRelationships",
queryKey: "GDAPRelationshipOnboarding-wizard",
});

Expand Down
Loading