Feat UI windows automatic enrollment (#12988)

relates to #12606

Implementation of the Windows automatic enrollment Fleet UI pages. This
includes implementation of card for windows automatic enrollment, the
setup page for windows automatic enrollment, and terms and conditions
page for windows (This is currently still being worked on as our current
solution is not working).

**windows mdm auto enrollment card**


![image](https://github.com/fleetdm/fleet/assets/1153709/d4dc1813-dc28-4a63-bacd-cb7e43e18170)

**windows auto enrollment setup page**


![image](https://github.com/fleetdm/fleet/assets/1153709/92da4b05-0d5d-4404-867f-6d315957bdc3)

- [x] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Manual QA for all new/changed functionality

---------

Co-authored-by: Marcos Oviedo <marcos@fleetdm.com>
This commit is contained in:
Gabriel Hernandez
2023-08-08 15:57:55 +01:00
committed by GitHub
co-authored by Marcos Oviedo
parent 6d186602cd
commit 6555d8def4
21 changed files with 642 additions and 66 deletions
@@ -0,0 +1 @@
- add windows mdmd automatic enrollment setup pages to fleet UI
@@ -3,7 +3,11 @@ import PropTypes from "prop-types";
import classnames from "classnames";
import { noop, pick } from "lodash";
import { stringToClipboard } from "utilities/copy_text";
import FormField from "components/forms/FormField";
import Button from "components/buttons/Button";
import Icon from "components/Icon";
const baseClass = "input-field";
@@ -34,6 +38,7 @@ class InputField extends Component {
PropTypes.arrayOf(PropTypes.string),
PropTypes.object,
]),
enableCopy: PropTypes.bool,
};
static defaultProps = {
@@ -50,6 +55,7 @@ class InputField extends Component {
parseTarget: false,
tooltip: "",
hint: "",
enableCopy: false,
};
componentDidMount() {
@@ -110,6 +116,12 @@ class InputField extends Component {
"tooltip",
]);
const copyValue = (e) => {
e.preventDefault();
stringToClipboard(value);
};
if (type === "textarea") {
return (
<FormField
@@ -135,25 +147,42 @@ class InputField extends Component {
);
}
const inputContainerClasses = classnames(`${baseClass}__input-container`, {
"copy-enabled": this.props.enableCopy,
});
return (
<FormField {...formFieldProps} type="input" className={inputWrapperClass}>
<input
disabled={disabled}
name={name}
id={name}
onChange={onInputChange}
onFocus={onFocus}
onBlur={onBlur}
className={inputClasses}
placeholder={placeholder}
ref={(r) => {
this.input = r;
}}
type={type}
{...inputOptions}
value={value}
autoComplete={blockAutoComplete ? "new-password" : ""}
/>
<div className={inputContainerClasses}>
<input
disabled={disabled}
name={name}
id={name}
onChange={onInputChange}
onFocus={onFocus}
onBlur={onBlur}
className={inputClasses}
placeholder={placeholder}
ref={(r) => {
this.input = r;
}}
type={type}
{...inputOptions}
value={value}
autoComplete={blockAutoComplete ? "new-password" : ""}
/>
<div className={`${baseClass}__copy-wrapper`}>
{this.props.enableCopy && (
<Button
variant="text-icon"
onClick={copyValue}
className={`${baseClass}__copy-value-button`}
>
<Icon name="copy" /> Copy
</Button>
)}
</div>
</div>
</FormField>
);
}
@@ -80,6 +80,12 @@
font-family: "SourceCodePro", $monospace;
}
}
&__input-container.copy-enabled {
display: flex;
align-items: center;
gap: $pad-medium;
}
}
// Removes arrows on Firefox number fields
@@ -0,0 +1,17 @@
import { Meta, StoryObj } from "@storybook/react";
import InputFieldHiddenContent from ".";
const meta: Meta<typeof InputFieldHiddenContent> = {
component: InputFieldHiddenContent,
title: "Components/FormFields/InputFieldHiddenContent",
args: {
value: "test value",
},
};
export default meta;
type Story = StoryObj<typeof InputFieldHiddenContent>;
export const Default: Story = {};
@@ -64,7 +64,7 @@ const AutomaticEnrollment = ({ router }: IAutomaticEnrollment) => {
return (
<div className={baseClass}>
<div className={`${baseClass}__section`}>
<AppleBusinessManagerSection />
<AppleBusinessManagerSection router={router} />
</div>
<div className={`${baseClass}__section`}>
<IdpSection />
@@ -0,0 +1,150 @@
import React, { useContext } from "react";
import PATHS from "router/paths";
import { AppContext } from "context/app";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import BackLink from "components/BackLink";
import MainContent from "components/MainContent";
import CustomLink from "components/CustomLink/CustomLink";
const generateMdmTermsOfUseUrl = (domain: string) => {
return `${domain}/api/mdm/microsoft/tos`;
};
const generateMdmDiscoveryUrl = (domain: string) => {
return `${domain}/api/mdm/microsoft/discovery`;
};
const baseClass = "windows-automatic-enrollment-page";
const WindowsAutomaticEnrollmentPage = () => {
const { config } = useContext(AppContext);
return (
<MainContent className={baseClass}>
<>
<BackLink
text="Back to automatic enrollment"
path={PATHS.ADMIN_INTEGRATIONS_AUTOMATIC_ENROLLMENT}
className={`${baseClass}__back-to-automatic-enrollment`}
/>
<h1>Azure Active Directory</h1>
<p>
The end user will see Microsoft&apos;s default initial setup. You can
further simplify the initial device setup with Autopilot, which is
similar to Apple&apos;s Automated Device Enrollment (DEP).{" "}
<CustomLink
newTab
text="Learn more"
url="https://fleetdm.com/docs/using-fleet/windows-mdm-setup"
/>
</p>
<ol className={`${baseClass}__setup-list`}>
<li>
<CustomLink
newTab
text="Sign in to Azure portal"
url="https://azure.microsoft.com/en-gb/get-started/azure-portal"
/>
</li>
<li>
Select <b>Azure Active Directory &gt; Custom domain names</b>, then
select <b>+ Add custom domain</b>, type your organization&apos;s
domain name (e.g. acme.com), and select <b>Add domain</b>.
</li>
<li>
Use the information presented in Azure AD to create a new TXT/MX
record with your domain registrar, then select <b>Verify</b>.
</li>
<li>
Select <b>Azure Active Directory &gt; Mobility (MDM and MAM)</b> (or
search for Mobility at the top of the page).
</li>
<li>
Select <b>+ Add application</b>, then select{" "}
<b>+ Create your own application</b>.
</li>
<li>
Enter Fleet as the name of your application and select{" "}
<b>Create</b>.
</li>
<li>
Set MDM user scope to <b>All</b>, then copy the URLs below, paste
them in Azure AD, and select <b>Save</b>.
<div className={`${baseClass}__url-inputs-wrapper`}>
<InputField
inputWrapperClass={`${baseClass}__url-input`}
label="MDM terms of use URL"
name="mdmTermsOfUseUrl"
tooltip="The terms of use URL is used to display the terms of service to end users
before turning on MDM their host. The terms of use text informs users about
policies that will be enforced on the host."
value={generateMdmTermsOfUseUrl(
config?.server_settings.server_url || ""
)}
enableCopy
/>
<InputField
inputWrapperClass={`${baseClass}__url-input`}
label="MDM discovery URL"
name="mdmDiscoveryUrl"
tooltip="The enrollment URL is used to connect hosts with the MDM service."
value={generateMdmDiscoveryUrl(
config?.server_settings.server_url || ""
)}
enableCopy
/>
</div>
</li>
<li>
Go back to <b>Mobility (MDM and MAM)</b>, refresh the page, then
open newly created app and select{" "}
<b>On-premises MDM application settings</b>.
</li>
<li>
Select the link under <b>Application ID URI</b>, then select{" "}
<b>Edit</b> button next to the Application ID URI input.
</li>
<li>
Use your organization&apos;s domain you added in the first step, and
select <b>Save</b>.
</li>
<li>
Select <b>API permissions</b> from the sidebar, then select{" "}
<b>+ Add permissions</b>.
</li>
<li>
Select <b>Microsoft Graph</b>, then select{" "}
<b>Delegated permissions</b>, and select{" "}
<b>Group &gt; Group.Read.All</b> and{" "}
<b>Group &gt; Group.ReadWrite.All</b>.
</li>
<li>
Select <b>Application permissions</b>, then select following:
<ul className={`${baseClass}__permissions-list`}>
<li>Device.Read.All</li>
<li>Device &gt; Device.ReadWrite.All</li>
<li>Directory &gt; Directory.Read.All</li>
<li>Group &gt; Group.Read.All</li>
<li>User &gt; User.Read.All</li>
</ul>
</li>
<li>
Select <b>Add permissions</b>.
</li>
<li>
Select <b>Grant admin consent for &lt;your tenant name&gt;</b>, and
confirm.
</li>
<li>
You&apos;re ready to automatically enroll Windows hosts to Fleet.
</li>
</ol>
</>
</MainContent>
);
};
export default WindowsAutomaticEnrollmentPage;
@@ -0,0 +1,45 @@
.windows-automatic-enrollment-page {
&__back-to-automatic-enrollment {
margin-bottom: $pad-xlarge;
}
h1 {
margin-bottom: $pad-xxlarge;
font-size: $x-large;
}
p {
font-size: $x-small;
margin: 0 0 $pad-large;
}
&__setup-list {
font-size: $x-small;
display: flex;
flex-direction: column;
gap: $pad-large;
padding: 0;
margin: 0;
max-width: 660px;
list-style-position: inside;
}
&__url-inputs-wrapper {
display: flex;
flex-direction: column;
gap: $pad-icon;
margin-top: $pad-large;
}
&__url-input {
margin-bottom: 0;
}
&__permissions-list {
margin-top: $pad-large;
list-style: disc;
display: flex;
flex-direction: column;
gap: $pad-medium;
}
}
@@ -0,0 +1 @@
export { default } from "./WindowsAutomaticEnrollmentPage";
@@ -2,11 +2,14 @@ import React, { useContext, useState } from "react";
import { useQuery } from "react-query";
import { AxiosError } from "axios";
import FileSaver from "file-saver";
import { InjectedRouter } from "react-router";
import PATHS from "router/paths";
import { IMdmAppleBm } from "interfaces/mdm";
import mdmAppleBmAPI from "services/entities/mdm_apple_bm";
import { readableDate } from "utilities/helpers";
import { NotificationContext } from "context/notification";
import { AppContext } from "context/app";
import Icon from "components/Icon";
import Button from "components/buttons/Button";
@@ -16,6 +19,7 @@ import DataError from "components/DataError";
import Spinner from "components/Spinner/Spinner";
import EditTeamModal from "../EditTeamModal";
import WindowsAutomaticEnrollmentCard from "./components/WindowsAutomaticEnrollmentCard/WindowsAutomaticEnrollmentCard";
const baseClass = "apple-business-manager-section";
@@ -24,10 +28,17 @@ interface IABMKeys {
decodedPrivate: string;
}
const AppleBusinessManagerSection = () => {
interface IAppleBusinessManagerSectionProps {
router: InjectedRouter;
}
const AppleBusinessManagerSection = ({
router,
}: IAppleBusinessManagerSectionProps) => {
const [showEditTeamModal, setShowEditTeamModal] = useState(false);
const [defaultTeamName, setDefaultTeamName] = useState("No team");
const { renderFlash } = useContext(NotificationContext);
const { config } = useContext(AppContext);
const {
data: mdmAppleBm,
@@ -58,6 +69,10 @@ const AppleBusinessManagerSection = () => {
setShowEditTeamModal(!showEditTeamModal);
};
const navigateToWindowsAutomaticEnrollment = () => {
router.push(PATHS.ADMIN_INTEGRATIONS_AUTOMATIC_ENROLLMENT_WINDOWS);
};
const onDownloadKeys = (evt: React.MouseEvent) => {
evt.preventDefault();
@@ -219,6 +234,11 @@ const AppleBusinessManagerSection = () => {
<div className={baseClass}>
<h2>Apple Business Manager</h2>
{isLoadingMdmAppleBm ? <Spinner /> : renderAppleBMInfo()}
{config?.mdm_enabled && (
<WindowsAutomaticEnrollmentCard
viewDetails={navigateToWindowsAutomaticEnrollment}
/>
)}
{showEditTeamModal && (
<EditTeamModal
onCancel={toggleEditTeamModal}
@@ -0,0 +1,34 @@
import React from "react";
import { noop } from "lodash";
import Card from "components/Card";
import Button from "components/buttons/Button";
import Icon from "components/Icon/Icon";
const baseClass = "windows-automatic-enrollment-card";
interface IWindowsAutomaticEnrollmentCardProps {
viewDetails: () => void;
}
const WindowsAutomaticEnrollmentCard = ({
viewDetails,
}: IWindowsAutomaticEnrollmentCardProps) => {
return (
<Card className={baseClass} color="gray">
<div>
<h3>Windows automatic enrollment</h3>
<p>
To use automatic enrollment for Windows hosts and Windows Autopilot
you need to connect Fleet to Azure AD first.
</p>
</div>
<Button onClick={viewDetails} variant="text-icon">
Details{" "}
<Icon name="chevron" direction="right" color="core-fleet-blue" />
</Button>
</Card>
);
};
export default WindowsAutomaticEnrollmentCard;
@@ -0,0 +1,18 @@
.windows-automatic-enrollment-card {
margin-top: $pad-xxxlarge;
font-size: $x-small;
display: flex;
justify-content: space-between;
align-items: center;
h3 {
font-size: $x-small;
font-weight: $bold;
margin: 0 0 $pad-xsmall;
}
p {
margin: 0;
max-width: 520px;
}
}
+5
View File
@@ -55,6 +55,7 @@ import MacOSSettings from "pages/ManageControlsPage/MacOSSettings";
import MacOSSetup from "pages/ManageControlsPage/MacOSSetup/MacOSSetup";
import WindowsMdmPage from "pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage";
import MacOSMdmPage from "pages/admin/IntegrationsPage/cards/MdmSettings/MacOSMdmPage";
import WindowsAutomaticEnrollmentPage from "pages/admin/IntegrationsPage/cards/AutomaticEnrollment/WindowsAutomaticEnrollmentPage";
import PATHS from "router/paths";
@@ -141,6 +142,10 @@ const routes = (
</Route>
<Route path="integrations/mdm/windows" component={WindowsMdmPage} />
<Route path="integrations/mdm/apple" component={MacOSMdmPage} />
<Route
path="integrations/automatic-enrollment/windows"
component={WindowsAutomaticEnrollmentPage}
/>
<Route path="teams" component={TeamDetailsWrapper}>
<Route path="members" component={MembersPage} />
<Route path="options" component={AgentOptionsPage} />
+4
View File
@@ -17,6 +17,8 @@ export default {
DASHBOARD_MAC: `${URL_PREFIX}/dashboard/mac`,
DASHBOARD_WINDOWS: `${URL_PREFIX}/dashboard/windows`,
DASHBOARD_CHROME: `${URL_PREFIX}/dashboard/chrome`,
// Admin pages
ADMIN_USERS: `${URL_PREFIX}/settings/users`,
ADMIN_INTEGRATIONS: `${URL_PREFIX}/settings/integrations`,
ADMIN_INTEGRATIONS_TICKET_DESTINATIONS: `${URL_PREFIX}/settings/integrations/ticket-destinations`,
@@ -24,6 +26,7 @@ export default {
ADMIN_INTEGRATIONS_MDM_MAC: `${URL_PREFIX}/settings/integrations/mdm/apple`,
ADMIN_INTEGRATIONS_MDM_WINDOWS: `${URL_PREFIX}/settings/integrations/mdm/windows`,
ADMIN_INTEGRATIONS_AUTOMATIC_ENROLLMENT: `${URL_PREFIX}/settings/integrations/automatic-enrollment`,
ADMIN_INTEGRATIONS_AUTOMATIC_ENROLLMENT_WINDOWS: `${URL_PREFIX}/settings/integrations/automatic-enrollment/windows`,
ADMIN_TEAMS: `${URL_PREFIX}/settings/teams`,
ADMIN_SETTINGS: `${URL_PREFIX}/settings`,
ADMIN_SETTINGS_INFO: `${URL_PREFIX}/settings/organization/info`,
@@ -35,6 +38,7 @@ export default {
ADMIN_SETTINGS_STATISTICS: `${URL_PREFIX}/settings/organization/statistics`,
ADMIN_SETTINGS_ADVANCED: `${URL_PREFIX}/settings/organization/advanced`,
ADMIN_SETTINGS_FLEET_DESKTOP: `${URL_PREFIX}/settings/organization/fleet-desktop`,
EDIT_PACK: (packId: number): string => {
return `${URL_PREFIX}/packs/${packId}/edit`;
},
+258
View File
@@ -0,0 +1,258 @@
<!-- This is the html page for a user accepting the terms of service during the
windows mdm enrollment flow. -->
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
}
.mdm-windows-sso-callback-page {
font-family: "Inter", sans-serif;
height: 100vh;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.main-content {
padding: 0px;
}
.eula-wrapper {
width: 80vw;
display: flex;
flex-direction: column;
align-items: center;
}
.eula {
font-size: 18px;
width: 600px;
height: 400px;
overflow-y: scroll;
}
.eula ul {
list-style: none;
}
.eula ol {
padding: 0;
list-style-position: inside;
}
h1 {
font-size: 28px;
font-weight: bold;
margin-top: 0;
margin-bottom: 42px;
}
button {
border: none;
border-radius: 4px;
cursor: pointer;
color: white;
background-color: #192147;
margin-top: 16px;
padding: 8px 16px;
font-size: 18px;
width: 341px;
height: 78px;
}
</style>
<script type="text/javascript">
const agreeToTerms = () => {
window.location =
"{{.RedirectURL}}" + "?IsAccepted=true&OpaqueBlob={{.ClientData}}";
};
</script>
</head>
<body>
<div class="main-content">
<div class="mdm-windows-sso-callback-page">
<div class="eula-wrapper">
<h1>Terms and conditions</h1>
<div class="eula">
<p>
Fleet is open-source software for managing computers. Your
organization is using it to manage and secure this computer.
</p>
<p>Fleet does NOT have access to:</p>
<ul>
<li>⌨️ Keystrokes</li>
<li>🖱 Mouse movements</li>
<li>🎙 Webcams & mic</li>
<li>🖥 Screen content</li>
</ul>
<p>
To read the source code running on your device, visit
github.com/fleetdm/fleet.
</p>
<p>
The people who contribute to Fleet are helpful, kind, and
dedicated to clarity through open-source software. If you run into
issues or would like to contribute, let us know at fleetdm.com.
</p>
<p>And now, the End User License Agreement:</p>
<p>
This copy of Fleet (“the Software Product”) and accompanying
documentation is licensed and not sold. Fleet Device Management,
Inc (“Fleet”) owns intellectual property rights in the Software
Product that are protected by laws and treaties pertaining to
copyright and other forms of intellectual property. The
Licensee&apos;s (“you” or “your”) license to download, use, copy,
or change the Software Product is subject to these rights and to
all the terms and conditions of this End User License Agreement
(“Agreement”). These Terms are subject to change by Fleet Device
Management, Inc without prior written notice at any time, and in
Fleet&apos;s sole discretion.
</p>
<ol>
<li>
Acceptance<br />
<p>
You accept and agree to be bound by the terms of this
agreement by selecting the “accept” option and downloading the
software product or by installing, using or copying the
software product. You must agree to all of the terms of this
agreement before downloading or installing the software
product.
</p>
</li>
<li>
License Grant<br />
<p>
Subject to the terms and conditions of this Agreement, Fleet
hereby grants to End User, and End User&amp;s authorized users
who have been registered with Fleet, a non-exclusive and
nontransferable license, during the term of your contract, to
access and use the Software Product. End User agrees that its
purchase of subscription(s) for the Software Product is
neither contingent upon the delivery of any future
functionality or features nor dependent upon any oral or
written public comments made by Fleet with respect to future
functionality or features, unless specifically documented in a
statement of work or other agreement. Upon expiration or
termination of your contract, the rights and licenses granted
hereunder will automatically terminate, and End Users may
continue to use the free version of the Software Product but
will lose functionality associated with paid features.
</p>
</li>
<li>
Restrictions on Transfer<br />
<p>
Without first obtaining the express written consent of Fleet
Device Management Inc., you may not assign your rights and
obligations under this Agreement, or redistribute, encumber,
sell, rent, lease, sublicense, or otherwise transfer your
rights to the Software Product.
</p>
</li>
<li>
Restrictions on Use<br />
<p>
You may not use, copy, or install the Software Product on more
hosts than the number permitted by license.
</p>
</li>
<li>
No Warranties<br />
<p>
To the maximum extent permitted by applicable law, Fleet
expressly disclaims any warranty for the Software Product. The
Software Product and any related documentation are provided
"as is" without warranty of any kind, either express or
implied, including, without limitation, the implied warranties
of merchantability or fitness for a particular purpose. Fleet
makes no warranty that the Software Product will meet your
requirements or operate under your specific conditions of use.
Fleet makes no warranty that operation of the Software Product
will be secure, error free, or free from interruption. The
entire risk arising out of use or performance of the Software
Product remains with you.
</p>
</li>
<li>
No Liability for Consequential Damages<br />
<p>
To the maximum extent permitted by applicable law, in no event
shall Fleet, its directors, officers, employees, agents or its
suppliers be liable for any damages whatsoever (including,
without limitation, damages for loss of business profit,
business interruption, loss of business information, or any
other pecuniary loss) arising out of the use of, or inability
to use, this Fleet product, even if Fleet has been advised of
the possibility of such damages. Because some
states/jurisdictions do not allow the exclusion or limitation
of liability for consequential or incidental damages, the
above limitation may not apply to you.
</p>
</li>
<li>
Limitation of Remedies and Damages<br />
<p>
Your remedy for a breach of this Agreement or of any warranty
included in this Agreement is the correction or replacement of
the Software Product. Selection of whether to correct or
replace shall be solely at the discretion of Fleet. If Fleet
is unable to provide a replacement or substitute Software
Product or corrections to the Software Product, your sole
alternate remedy shall be a refund of the purchase price for
the Software Product.
</p>
</li>
<li>
Governing Law<br />
<p>
This Agreement is governed by the laws of the State of
California, without regard to its conflict of law provisions.
</p>
</li>
<li>
Termination<br />
<p>
Without prejudice to any other rights, Fleet may terminate
this Agreement if you fail to comply with the terms and
conditions of this EULA.
</p>
</li>
<li>
Severability<br />
<p>
If any provision of this Agreement shall be held to be invalid
or unenforceable, the remainder of this Agreement shall remain
in full force and effect. To the extent any express or implied
restrictions are not permitted by applicable laws, these
express or implied restrictions shall remain in force and
effect to the maximum extent permitted by such applicable
laws.
</p>
</li>
<li>
Export Restrictions<br />
<p>
You agree that neither you nor your customers intend to or
will, directly or indirectly, export or transmit the Software
Product or related documentation and technical data, or
process, or service that is the direct product of the Software
Product to any country to which such export or transmission is
restricted by any applicable U.S. regulation or statute,
without the prior written consent, if required, of the Bureau
of Export Administration of the U.S. Department of Commerce,
or such other governmental entity as may have jurisdiction
over such export or transmission.
</p>
</li>
</ol>
</div>
<button onClick="agreeToTerms()">Agree and continue</button>
</div>
</div>
</div>
</body>
</html>
+5
View File
@@ -16,3 +16,8 @@ declare module "*.gif" {
const value: string;
export = value;
}
declare module "*.pdf" {
const value: string;
export = value;
}
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"html/template"
"github.com/fleetdm/fleet/v4/server"
"github.com/fleetdm/fleet/v4/server/fleet"
)
@@ -17,7 +18,7 @@ type InviteMailer struct {
}
func (i *InviteMailer) Message() ([]byte, error) {
t, err := getTemplate("server/mail/templates/invite_token.html")
t, err := server.GetTemplate("server/mail/templates/invite_token.html", "email_template")
if err != nil {
return nil, err
}
+2 -16
View File
@@ -12,7 +12,7 @@ import (
"strings"
"time"
"github.com/fleetdm/fleet/v4/server/bindata"
"github.com/fleetdm/fleet/v4/server"
"github.com/fleetdm/fleet/v4/server/config"
"github.com/fleetdm/fleet/v4/server/fleet"
)
@@ -266,7 +266,7 @@ type SMTPTestMailer struct {
}
func (m *SMTPTestMailer) Message() ([]byte, error) {
t, err := getTemplate("server/mail/templates/smtp_setup.html")
t, err := server.GetTemplate("server/mail/templates/smtp_setup.html", "email_template")
if err != nil {
return nil, err
}
@@ -278,17 +278,3 @@ func (m *SMTPTestMailer) Message() ([]byte, error) {
return msg.Bytes(), nil
}
func getTemplate(templatePath string) (*template.Template, error) {
templateData, err := bindata.Asset(templatePath)
if err != nil {
return nil, err
}
t, err := template.New("email_template").Parse(string(templateData))
if err != nil {
return nil, err
}
return t, nil
}
+4 -2
View File
@@ -3,6 +3,8 @@ package mail
import (
"bytes"
"html/template"
"github.com/fleetdm/fleet/v4/server"
)
type ChangeEmailMailer struct {
@@ -12,7 +14,7 @@ type ChangeEmailMailer struct {
}
func (cem *ChangeEmailMailer) Message() ([]byte, error) {
t, err := getTemplate("server/mail/templates/change_email_confirmation.html")
t, err := server.GetTemplate("server/mail/templates/change_email_confirmation.html", "email_template")
if err != nil {
return nil, err
}
@@ -34,7 +36,7 @@ type PasswordResetMailer struct {
}
func (r PasswordResetMailer) Message() ([]byte, error) {
t, err := getTemplate("server/mail/templates/password_reset.html")
t, err := server.GetTemplate("server/mail/templates/password_reset.html", "email_template")
if err != nil {
return nil, err
}
+2 -28
View File
@@ -18,6 +18,7 @@ import (
"text/template"
"time"
"github.com/fleetdm/fleet/v4/server"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/logging"
"github.com/fleetdm/fleet/v4/server/fleet"
@@ -1165,34 +1166,7 @@ func (svc *Service) GetMDMWindowsManagementResponse(ctx context.Context, reqSync
// GetMDMWindowsTOSContent returns valid TOC content
func (svc *Service) GetMDMWindowsTOSContent(ctx context.Context, redirectUri string, reqID string) (string, error) {
tmpl, err := template.New("").Parse(`
<html>
<head>
<style>
button {
background-color: #008CBA;
color: white;
padding: 10px 60px;
border: none;
cursor: pointer;
}
</style>
</head>
<body>
<center>
<img src="https://fleetdm.com/images/logo-blue-162x92@2x.png">
<br>
<h2>Terms and conditions</h2>
<br> Terms and Conditions PDF content should go here <center>
<br>
<button type="button" onClick="acceptBtn()">Accept</button>
<script>
function acceptBtn() {
window.location = "{{.RedirectURL}}" + "?IsAccepted=true&OpaqueBlob={{.ClientData}}";
}
</script>
</body>
</html>`)
tmpl, err := server.GetTemplate("frontend/templates/windowsTOS.html", "windows-tos")
if err != nil {
return "", ctxerr.Wrap(ctx, err, "issue generating TOS content")
}
+19
View File
@@ -11,6 +11,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"net/url"
@@ -18,6 +19,7 @@ import (
"time"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server/bindata"
)
// GenerateRandomText return a string generated by filling in keySize bytes with
@@ -119,3 +121,20 @@ func DecodePrivateKeyPEM(encoded []byte) (*rsa.PrivateKey, error) {
return x509.ParsePKCS1PrivateKey(block.Bytes)
}
// GetTemplate takes a path to a template file and a template name and will
// include the template file in the build binary. It then returns a pointer to
// the template.
func GetTemplate(templatePath string, templateName string) (*template.Template, error) {
templateData, err := bindata.Asset(templatePath)
if err != nil {
return nil, err
}
t, err := template.New(templateName).Parse(string(templateData))
if err != nil {
return nil, err
}
return t, nil
}