Skip to content

Commit

Permalink
Normalize User object
Browse files Browse the repository at this point in the history
When retrieving user data from an API call,
the values received can be undefined. This
makes difficult to determine any modification
in the data (i.e.: 'Revert' functionality).

Ideally, the data received should be
initialized to an empty default values
(depending on the type of parameter).

Signed-off-by: Carla Martinez <[email protected]>
  • Loading branch information
carma12 committed Nov 14, 2023
1 parent 42ad866 commit 2563704
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 80 deletions.
134 changes: 70 additions & 64 deletions src/components/Form/IpaCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
InputGroup,
TimePicker,
isValidDate,
yyyyMMddFormat,
} from "@patternfly/react-core";
// Utils
import {
Expand Down Expand Up @@ -42,26 +41,15 @@ export interface ParamPropertiesDateTime {

const IpaCalendar = (props: IPAParamDefinition) => {
const getValueDateTime = (ipaObject, propName) => {
let paramUtcDate: Date | null = null;

if (ipaObject !== undefined) {
const paramValue = ipaObject[propName] as DateParam[];

if (paramValue) {
// Detect if date parameter exists
let dateParam = "";
// Processing different formats
// i.e.: 'paramValue[0].__datetime__' or 'paramValue'
if (paramValue[0].__datetime__) {
dateParam = paramValue[0].__datetime__ as string;
} else {
dateParam = paramValue.toString();
}
// Parse to UTC format
paramUtcDate = parseFullDateStringToUTCFormat(dateParam);
}
if (
!ipaObject ||
ipaObject[propName] === undefined ||
// Fallback in case the parameter is an empty array
(Array.isArray(ipaObject[propName]) && ipaObject[propName].length === 0)
) {
return null;
}
return paramUtcDate;
return ipaObject[propName];
};

function getParamPropertiesDateTime(
Expand Down Expand Up @@ -110,14 +98,33 @@ const IpaCalendar = (props: IPAParamDefinition) => {
}

const { readOnly, value } = getParamPropertiesDateTime(props);
const [valueDate, setValueDate] = React.useState<Date | null>(value || null);
const [valueDate, setValueDate] = React.useState<Date | null>(value as Date);
const [date, setDate] = React.useState<string>("");
const [hour, setHour] = React.useState<string>("");

// Keep the values updated, thus preventing empty values
React.useEffect(() => {
if (props.ipaObject !== undefined) {
setValueDate(value || null);
if (props.ipaObject !== undefined && value !== null) {
if (!isValidDate(value)) {
const transformedDate = parseFullDateStringToUTCFormat(
value.toString()
);
setValueDate(transformedDate);
} else {
setValueDate(value);
}
}
}, [props.ipaObject, value]);

React.useEffect(() => {
if (valueDate !== null) {
setDate(yyyyMMddFormat(valueDate));
setHour(hhMMFormat(valueDate));
} else {
setDate("");
setHour("");
}
}, [props.ipaObject]);
}, [valueDate]);

// On change date handler
const onDateChange = (
Expand All @@ -126,15 +133,6 @@ const IpaCalendar = (props: IPAParamDefinition) => {
newFromDate: Date | undefined
) => {
if (newFromDate !== undefined) {
if (
valueDate &&
isValidDate(valueDate) &&
isValidDate(newFromDate) &&
inputDate === yyyyMMddFormat(newFromDate)
) {
newFromDate.setHours(valueDate.getHours());
newFromDate.setMinutes(valueDate.getMinutes());
}
if (
isValidDate(newFromDate) &&
inputDate === yyyyMMddFormat(newFromDate)
Expand All @@ -143,7 +141,7 @@ const IpaCalendar = (props: IPAParamDefinition) => {
// Update 'ipaObject' with the new date
// - Parse to generalized format (to return to the "user_mod" API call)
if (props.ipaObject !== undefined && props.onChange !== undefined) {
const LDAPDate = toGeneralizedTime(newFromDate);
const LDAPDate: string = toGeneralizedTime(newFromDate);
updateIpaObject(
props.ipaObject,
props.onChange,
Expand All @@ -157,56 +155,64 @@ const IpaCalendar = (props: IPAParamDefinition) => {

// On change time handler
const onTimeChange = (_event, time, hour, minute) => {
// Assume inital data is null
// If the date is empty, create a new one with the current time
let updatedFromDate: Date = new Date();
if (valueDate && isValidDate(valueDate)) {
const updatedFromDate = valueDate;
updatedFromDate.setHours(hour);
updatedFromDate.setMinutes(minute);

setValueDate(updatedFromDate);
// Update 'ipaObject' with the new date
// - Parse to generalized format (to return to the "user_mod" API call)
if (props.ipaObject !== undefined && props.onChange !== undefined) {
const LDAPDate = toGeneralizedTime(updatedFromDate);
updateIpaObject(props.ipaObject, props.onChange, LDAPDate, props.name);
}
updatedFromDate = valueDate;
}
// If the date is empty, create a new one with the current time
if (valueDate === null) {
const updatedFromDate = new Date();
updatedFromDate.setHours(hour);
updatedFromDate.setMinutes(minute);

setValueDate(updatedFromDate);
// Update 'ipaObject' with the new date
// - Parse to generalized format (to return to the "user_mod" API call)
if (props.ipaObject !== undefined && props.onChange !== undefined) {
const LDAPDate = toGeneralizedTime(updatedFromDate);
updateIpaObject(props.ipaObject, props.onChange, LDAPDate, props.name);
}
updatedFromDate.setHours(hour);
updatedFromDate.setMinutes(minute);

setValueDate(updatedFromDate);
// Update 'ipaObject' with the new date
// - Parse to generalized format (to return to the "user_mod" API call)
if (props.ipaObject !== undefined && props.onChange !== undefined) {
const LDAPDate = toGeneralizedTime(updatedFromDate);
updateIpaObject(props.ipaObject, props.onChange, LDAPDate, props.name);
}
};

// Parse the current date into 'YYYY-MM-DD' format
const yyyyMMddFormat = (date: Date): string => {
if (date === undefined || date === null) return "";

// This convertion is needed to prevent any Date data type issues
const dt = new Date(date);
const year = dt.getFullYear();
const month = dt.getMonth() + 1;
const day = dt.getDate().toString().padStart(2, "0");

const res = year.toString() + "-" + month.toString() + "-" + day;
return res;
};

// Parse the current date into 'HH:MM' format
const hhMMFormat = (date: Date) => {
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
const hhMMFormat = (date: Date): string => {
if (date === undefined || date === null) return "";

// This convertion is needed to prevent any Date data type issues
const dt = new Date(date);
const hours = dt.getHours().toString().padStart(2, "0");
const minutes = dt.getMinutes().toString().padStart(2, "0");

return hours + ":" + minutes;
const res = hours + ":" + minutes;
return res;
};

return (
<InputGroup>
<DatePicker
name={props.name}
value={valueDate !== null ? yyyyMMddFormat(valueDate) : ""}
value={date}
onChange={onDateChange}
aria-label="Kerberos principal expiration date"
placeholder="YYYY-MM-DD"
isDisabled={readOnly}
/>
<TimePicker
name={props.name}
time={valueDate !== null ? hhMMFormat(valueDate) : ""}
time={hour}
aria-label="Kerberos principal expiration time"
onChange={onTimeChange}
placeholder="HH:MM"
Expand Down
4 changes: 3 additions & 1 deletion src/components/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
User,
IDPServer,
RadiusServer,
PwPolicy,
} from "src/utils/datatypes/globalDataTypes";
// Layouts
import ToolbarLayout from "src/components/layouts/ToolbarLayout";
Expand Down Expand Up @@ -48,7 +49,8 @@ export interface PropsToUserSettings {
onUserChange: (user: Partial<User>) => void;
metadata: Metadata;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pwPolicyData: any;
// pwPolicyData: any;
pwPolicyData: PwPolicy;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
krbPolicyData: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
12 changes: 8 additions & 4 deletions src/hooks/useUserSettingsData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
// Data types
import {
IDPServer,
KrbPolicy,
Metadata,
PwPolicy,
RadiusServer,
User,
} from "src/utils/datatypes/globalDataTypes";
Expand All @@ -25,8 +27,10 @@ type UserSettingsData = {
originalUser: Partial<User>;
user: Partial<User>;
setUser: (user: Partial<User>) => void;
pwPolicyData?: Record<string, unknown>;
krbtPolicyData?: Record<string, unknown>;
// pwPolicyData?: Record<string, unknown>;
pwPolicyData: PwPolicy;
// krbtPolicyData?: Record<string, unknown>;
krbtPolicyData: KrbPolicy;
certData?: Record<string, unknown>;
refetch: () => void;
modifiedValues: () => Partial<User>;
Expand Down Expand Up @@ -85,8 +89,8 @@ const useUserSettingsData = (userId: string): UserSettingsData => {

if (userFullData) {
settings.originalUser = userFullData.user || {};
settings.pwPolicyData = userFullData.pwPolicy;
settings.krbtPolicyData = userFullData.krbtPolicy;
settings.pwPolicyData = userFullData.pwPolicy as PwPolicy;
settings.krbtPolicyData = userFullData.krbtPolicy as KrbPolicy;
settings.certData = userFullData.cert;
} else {
settings.originalUser = {};
Expand Down
38 changes: 31 additions & 7 deletions src/services/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ import {
IDPServer,
RadiusServer,
CertificateAuthority,
PwPolicy,
KrbPolicy,
} from "src/utils/datatypes/globalDataTypes";
import { apiToUser } from "src/utils/userUtils";
import { apiToUser, objectToUser } from "src/utils/userUtils";
import { apiToPwPolicy, objectToPwPolicy } from "src/utils/pwPolicyUtils";
import { apiToKrbPolicy, objectToKrbPolicy } from "src/utils/krbPolicyUtils";

export type UserFullData = {
user?: Partial<User>;
pwPolicy?: Record<string, unknown>;
krbtPolicy?: Record<string, unknown>;
pwPolicy?: Partial<PwPolicy>;
krbtPolicy?: Partial<KrbPolicy>;
cert?: Record<string, unknown>;
};

Expand Down Expand Up @@ -301,11 +305,31 @@ export const api = createApi({
},
transformResponse: (response: BatchResponse): UserFullData => {
const results = response.result.results;

// Initialize user data (to prevent 'undefined' values)
const userData = results[0].result;
const pwPolicyData = results[1].result;
const krbtPolicyData = results[2].result;
const certData = results[3].result;

const userObject = apiToUser(
objectToUser(userData) as unknown as Record<string, unknown>
);
const pwPolicyObject = apiToPwPolicy(
objectToPwPolicy(pwPolicyData) as unknown as Record<string, unknown>
);
const krbtPolicyObject = apiToKrbPolicy(
objectToKrbPolicy(krbtPolicyData) as unknown as Record<
string,
unknown
>
);

return {
user: apiToUser(results[0].result),
pwPolicy: results[1].result,
krbtPolicy: results[2].result,
cert: results[3].result,
user: userObject,
pwPolicy: pwPolicyObject,
krbtPolicy: krbtPolicyObject,
cert: certData,
};
},
providesTags: ["FullUser"],
Expand Down
4 changes: 3 additions & 1 deletion src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ const store = configureStore({
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
getDefaultMiddleware({
serializableCheck: false, // Removes the warning about non-serializable data
}).concat(api.middleware),
});

// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
Expand Down
38 changes: 36 additions & 2 deletions src/utils/datatypes/globalDataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export interface User {
// account
uid: string; // required
has_password: boolean;
krbpasswordexpiration: Date | string;
krbpasswordexpiration: string | any[]; // TODO; Remove 'string' when 'staged' and 'preserved' are adapted
uidnumber: string;
gidnumber: string;
krbprincipalname: string[]; // multivalue
krbprincipalexpiration: Date | string;
krbprincipalexpiration: Date | string | null; // TODO; Remove 'string' when 'staged' and 'preserved' are adapted
loginshell: string;
homedirectory: string;
ipasshpubkey: string[]; // multivalue
Expand Down Expand Up @@ -83,6 +83,40 @@ export interface User {
sshpubkeyfp: string[]; // multivalue
}

export interface PwPolicy {
attributelevelrights: Record<string, unknown>;
name: string;
dn: string;
krbmaxpwdlife: string;
krbminpwdlife: string;
krbpwdfailurecountinterval: string;
krbpwdhistorylength: string;
krbpwdlockoutduration: string;
krbpwdmaxfailure: string;
krbpwdmindiffchars: string;
krbpwdminlength: string;
passwordgracelimit: string;
}

export interface KrbPolicy {
attributelevelrights: Record<string, unknown>;
cn: string;
ipacertmapdata: string[];
ipantsecurityidentifier: string;
ipasshpubkey: string[];
ipauniqueid: string;
krbcanonicalname: string;
krbmaxrenewableage: string;
krbmaxticketlife: string;
krbprincipalexpiration: Date | string | null; // TODO: Remove 'string' when 'staged' and 'preserved' are adapted
krbprincipalname: string[];
loginshell: string;
mail: string[];
memberof: string;
mepmanagedentry: string;
usercertificatebinary: string[];
}

export interface UserGroup {
name: string;
gid: string;
Expand Down
Loading

0 comments on commit 2563704

Please sign in to comment.