grafana/public/app/features/admin/ldap/LdapUserPage.tsx
gotjosh e4afc8d518 LDAP: Fixing sync issues (#19446)
The arching goal of this commit is to enable single user
synchronisation with LDAP. Also, it included minor fixes of style,
error messages and minor bug fixing.

The changes are:

- bug: The `multildap` package has its own errors when the user is
  not found. We fixed the conditional branch on this error by asserting
on the `multildap` errors as opposed to the `ldap` one

- bug: The previous interface usage of `RevokeAllUserTokens` did not
  work as expected. This replaces the manual injection of the service by
leveraging the service injected as part of the `server` struct.

- chore: Better error messages around not finding the user in LDAP.

- fix: Enable the single sync button and disable it when we receive an
  error from LDAP. Please note, that you can enable it by dispatching
the error. This allows you to try again without having to reload the
page.

- fix: Move the sync info to the top, then move the sync button above
  that information and clearfix to have more harmony with the UI.
2019-11-07 14:31:44 +01:00

167 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import { NavModel } from '@grafana/data';
import { Alert } from '@grafana/ui';
import Page from 'app/core/components/Page/Page';
import { getNavModel } from 'app/core/selectors/navModel';
import {
AppNotificationSeverity,
LdapError,
LdapUser,
StoreState,
User,
UserSession,
SyncInfo,
LdapUserSyncInfo,
} from 'app/types';
import {
clearUserError,
loadLdapUserInfo,
revokeSession,
revokeAllSessions,
loadLdapSyncStatus,
syncUser,
} from '../state/actions';
import { LdapUserInfo } from './LdapUserInfo';
import { getRouteParamsId } from 'app/core/selectors/location';
import { UserSessions } from '../UserSessions';
import { UserInfo } from '../UserInfo';
import { UserSyncInfo } from '../UserSyncInfo';
interface Props {
navModel: NavModel;
userId: number;
user: User;
sessions: UserSession[];
ldapUser: LdapUser;
userError?: LdapError;
ldapSyncInfo?: SyncInfo;
loadLdapUserInfo: typeof loadLdapUserInfo;
clearUserError: typeof clearUserError;
loadLdapSyncStatus: typeof loadLdapSyncStatus;
syncUser: typeof syncUser;
revokeSession: typeof revokeSession;
revokeAllSessions: typeof revokeAllSessions;
}
interface State {
isLoading: boolean;
}
export class LdapUserPage extends PureComponent<Props, State> {
state = {
isLoading: true,
};
async componentDidMount() {
const { userId, loadLdapUserInfo, loadLdapSyncStatus } = this.props;
try {
await loadLdapUserInfo(userId);
await loadLdapSyncStatus();
} finally {
this.setState({ isLoading: false });
}
}
onClearUserError = () => {
this.props.clearUserError();
};
onSyncUser = () => {
const { syncUser, user } = this.props;
if (syncUser && user) {
syncUser(user.id);
}
};
onSessionRevoke = (tokenId: number) => {
const { userId, revokeSession } = this.props;
revokeSession(tokenId, userId);
};
onAllSessionsRevoke = () => {
const { userId, revokeAllSessions } = this.props;
revokeAllSessions(userId);
};
isUserError = (): boolean => {
return !!(this.props.userError && this.props.userError.title);
};
render() {
const { user, ldapUser, userError, navModel, sessions, ldapSyncInfo } = this.props;
const { isLoading } = this.state;
const userSyncInfo: LdapUserSyncInfo = {};
if (ldapSyncInfo) {
userSyncInfo.nextSync = ldapSyncInfo.nextSync;
}
if (user) {
userSyncInfo.prevSync = (user as any).updatedAt;
}
return (
<Page navModel={navModel}>
<Page.Contents isLoading={isLoading}>
<div className="grafana-info-box">
This user is synced via LDAP All changes must be done in LDAP or mappings.
</div>
{userError && userError.title && (
<div className="gf-form-group">
<Alert
title={userError.title}
severity={AppNotificationSeverity.Error}
children={userError.body}
onRemove={this.onClearUserError}
/>
</div>
)}
{userSyncInfo && (
<UserSyncInfo syncInfo={userSyncInfo} onSync={this.onSyncUser} disableSync={this.isUserError()} />
)}
{ldapUser && <LdapUserInfo ldapUser={ldapUser} />}
{!ldapUser && user && <UserInfo user={user} />}
{sessions && (
<UserSessions
sessions={sessions}
onSessionRevoke={this.onSessionRevoke}
onAllSessionsRevoke={this.onAllSessionsRevoke}
/>
)}
</Page.Contents>
</Page>
);
}
}
const mapStateToProps = (state: StoreState) => ({
userId: getRouteParamsId(state.location),
navModel: getNavModel(state.navIndex, 'global-users'),
user: state.ldapUser.user,
ldapUser: state.ldapUser.ldapUser,
userError: state.ldapUser.userError,
ldapSyncInfo: state.ldapUser.ldapSyncInfo,
sessions: state.ldapUser.sessions,
});
const mapDispatchToProps = {
loadLdapUserInfo,
loadLdapSyncStatus,
syncUser,
revokeSession,
revokeAllSessions,
clearUserError,
};
export default hot(module)(
connect(
mapStateToProps,
mapDispatchToProps
)(LdapUserPage)
);