*/ | */ | ||||
import { getJSON, post, RequestData } from '../helpers/request'; | import { getJSON, post, RequestData } from '../helpers/request'; | ||||
export interface IdentityProvider { | |||||
backgroundColor: string; | |||||
iconPath: string; | |||||
key: string; | |||||
name: string; | |||||
} | |||||
export function getCurrentUser(): Promise<any> { | export function getCurrentUser(): Promise<any> { | ||||
return getJSON('/api/users/current'); | return getJSON('/api/users/current'); | ||||
} | } | ||||
return getJSON('/api/users/groups', data); | return getJSON('/api/users/groups', data); | ||||
} | } | ||||
export function getIdentityProviders(): Promise<any> { | |||||
export function getIdentityProviders(): Promise<{ identityProviders: IdentityProvider[] }> { | |||||
return getJSON('/api/users/identity_providers'); | return getJSON('/api/users/identity_providers'); | ||||
} | } | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import GlobalFooterContainer from './GlobalFooterContainer'; | import GlobalFooterContainer from './GlobalFooterContainer'; | ||||
import NavBar from '../../components/nav/NavBar'; | import NavBar from '../../components/nav/NavBar'; | ||||
/*:: | |||||
type Props = { | |||||
children?: React.Element<*> | Array<React.Element<*>>, | |||||
hideLoggedInInfo?: boolean | |||||
}; | |||||
*/ | |||||
export default class SimpleContainer extends React.PureComponent { | |||||
/*:: props: Props; */ | |||||
interface Props { | |||||
children?: React.ReactNode; | |||||
hideLoggedInInfo?: boolean; | |||||
} | |||||
export default class SimpleContainer extends React.PureComponent<Props> { | |||||
componentDidMount() { | componentDidMount() { | ||||
const html = document.querySelector('html'); | const html = document.querySelector('html'); | ||||
if (html) { | if (html) { | ||||
return ( | return ( | ||||
<div className="global-container"> | <div className="global-container"> | ||||
<div className="page-wrapper" id="container"> | <div className="page-wrapper" id="container"> | ||||
<NavBar className="navbar-global" id="global-navigation" height={30} /> | |||||
<NavBar className="navbar-global" height={30} /> | |||||
<div id="bd" className="page-wrapper-simple"> | <div id="bd" className="page-wrapper-simple"> | ||||
<div id="nonav" className="page-simple"> | <div id="nonav" className="page-simple"> |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { Link } from 'react-router'; | |||||
import OAuthProviders from './OAuthProviders'; | import OAuthProviders from './OAuthProviders'; | ||||
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { IdentityProvider } from '../../../api/users'; | |||||
/*:: | |||||
type Props = { | |||||
identityProviders: Array<{ | |||||
backgroundColor: string, | |||||
iconPath: string, | |||||
key: string, | |||||
name: string | |||||
}>, | |||||
onSubmit: (string, string) => void | |||||
}; | |||||
*/ | |||||
/*:: | |||||
type State = { | |||||
collapsed: boolean, | |||||
login: string, | |||||
password: string | |||||
}; | |||||
*/ | |||||
interface Props { | |||||
identityProviders: IdentityProvider[]; | |||||
onSubmit: (login: string, password: string) => void; | |||||
} | |||||
export default class LoginForm extends React.PureComponent { | |||||
/*:: props: Props; */ | |||||
/*:: state: State; */ | |||||
interface State { | |||||
collapsed: boolean; | |||||
login: string; | |||||
password: string; | |||||
} | |||||
constructor(props /*: Props */) { | |||||
export default class LoginForm extends React.PureComponent<Props, State> { | |||||
constructor(props: Props) { | |||||
super(props); | super(props); | ||||
this.state = { | this.state = { | ||||
collapsed: props.identityProviders.length > 0, | collapsed: props.identityProviders.length > 0, | ||||
}; | }; | ||||
} | } | ||||
handleSubmit = (event /*: Event */) => { | |||||
handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
this.props.onSubmit(this.state.login, this.state.password); | this.props.onSubmit(this.state.login, this.state.password); | ||||
}; | }; | ||||
handleMoreOptionsClick = (event /*: Event */) => { | |||||
handleMoreOptionsClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||||
event.preventDefault(); | event.preventDefault(); | ||||
this.setState({ collapsed: false }); | this.setState({ collapsed: false }); | ||||
}; | }; | ||||
handleLoginChange = (event: React.SyntheticEvent<HTMLInputElement>) => | |||||
this.setState({ login: event.currentTarget.value }); | |||||
handlePwdChange = (event: React.SyntheticEvent<HTMLInputElement>) => | |||||
this.setState({ password: event.currentTarget.value }); | |||||
render() { | render() { | ||||
return ( | return ( | ||||
<div id="login_form"> | <div id="login_form"> | ||||
id="login" | id="login" | ||||
name="login" | name="login" | ||||
className="login-input" | className="login-input" | ||||
maxLength="255" | |||||
maxLength={255} | |||||
required={true} | required={true} | ||||
autoFocus={true} | autoFocus={true} | ||||
placeholder={translate('login')} | placeholder={translate('login')} | ||||
value={this.state.login} | value={this.state.login} | ||||
onChange={e => this.setState({ login: e.target.value })} | |||||
onChange={this.handleLoginChange} | |||||
/> | /> | ||||
</div> | </div> | ||||
required={true} | required={true} | ||||
placeholder={translate('password')} | placeholder={translate('password')} | ||||
value={this.state.password} | value={this.state.password} | ||||
onChange={e => this.setState({ password: e.target.value })} | |||||
onChange={this.handlePwdChange} | |||||
/> | /> | ||||
</div> | </div> | ||||
<button name="commit" type="submit"> | <button name="commit" type="submit"> | ||||
{translate('sessions.log_in')} | {translate('sessions.log_in')} | ||||
</button> | </button> | ||||
<a className="spacer-left" href={window.baseUrl + '/'}> | |||||
<Link className="spacer-left" to="/"> | |||||
{translate('cancel')} | {translate('cancel')} | ||||
</a> | |||||
</Link> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</form> | </form> |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import PropTypes from 'prop-types'; | |||||
import * as React from 'react'; | |||||
import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||
import LoginForm from './LoginForm'; | import LoginForm from './LoginForm'; | ||||
import { doLogin } from '../../../store/rootActions'; | import { doLogin } from '../../../store/rootActions'; | ||||
import { getAppState } from '../../../store/rootReducer'; | import { getAppState } from '../../../store/rootReducer'; | ||||
import { getIdentityProviders } from '../../../api/users'; | |||||
import { IdentityProvider, getIdentityProviders } from '../../../api/users'; | |||||
import { getBaseUrl } from '../../../helpers/urls'; | |||||
class LoginFormContainer extends React.PureComponent { | |||||
/*:: mounted: boolean; */ | |||||
interface Props { | |||||
doLogin: (login: string, password: string) => Promise<void>; | |||||
location: { hash?: string; pathName: string; query: { return_to?: string } }; | |||||
} | |||||
static propTypes = { | |||||
location: PropTypes.object.isRequired | |||||
}; | |||||
interface State { | |||||
identityProviders?: IdentityProvider[]; | |||||
} | |||||
state = {}; | |||||
class LoginFormContainer extends React.PureComponent<Props, State> { | |||||
mounted: boolean; | |||||
state: State = {}; | |||||
componentDidMount() { | componentDidMount() { | ||||
this.mounted = true; | this.mounted = true; | ||||
handleSuccessfulLogin = () => { | handleSuccessfulLogin = () => { | ||||
const { location } = this.props; | const { location } = this.props; | ||||
const queryReturnTo = location.query['return_to']; | const queryReturnTo = location.query['return_to']; | ||||
const returnTo = queryReturnTo ? `${queryReturnTo}${location.hash}` : `${window.baseUrl}/`; | |||||
window.location = returnTo; | |||||
const returnTo = queryReturnTo ? `${queryReturnTo}${location.hash}` : `${getBaseUrl()}/`; | |||||
window.location.href = returnTo; | |||||
}; | }; | ||||
handleSubmit = (login /*: string */, password /*: string */) => { | |||||
this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => { | |||||
/* do nothing */ | |||||
}); | |||||
handleSubmit = (login: string, password: string) => { | |||||
this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => {}); | |||||
}; | }; | ||||
render() { | render() { | ||||
} | } | ||||
} | } | ||||
const mapStateToProps = state => ({ | |||||
const mapStateToProps = (state: any) => ({ | |||||
appState: getAppState(state) | appState: getAppState(state) | ||||
}); | }); | ||||
const mapDispatchToProps = { doLogin }; | const mapDispatchToProps = { doLogin }; | ||||
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer); | |||||
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer as any); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | ||||
import RecentHistory from '../../../app/components/RecentHistory'; | |||||
import { doLogout } from '../../../store/rootActions'; | import { doLogout } from '../../../store/rootActions'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import RecentHistory from '../../../app/components/RecentHistory'; | |||||
import { getBaseUrl } from '../../../helpers/urls'; | |||||
class Logout extends React.PureComponent { | |||||
interface Props { | |||||
doLogout: () => Promise<void>; | |||||
} | |||||
class Logout extends React.PureComponent<Props> { | |||||
componentDidMount() { | componentDidMount() { | ||||
this.props | |||||
.doLogout() | |||||
.then(() => { | |||||
this.props.doLogout().then( | |||||
() => { | |||||
RecentHistory.clear(); | RecentHistory.clear(); | ||||
window.location = window.baseUrl + '/'; | |||||
}) | |||||
.catch(() => { | |||||
/* do nothing */ | |||||
}); | |||||
window.location.href = getBaseUrl() + '/'; | |||||
}, | |||||
() => {} | |||||
); | |||||
} | } | ||||
render() { | render() { | ||||
const mapDispatchToProps = { doLogout }; | const mapDispatchToProps = { doLogout }; | ||||
export default connect(mapStateToProps, mapDispatchToProps)(Logout); | |||||
export default connect(mapStateToProps, mapDispatchToProps)(Logout as any); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { translateWithParameters } from '../../../helpers/l10n'; | import { translateWithParameters } from '../../../helpers/l10n'; | ||||
import { IdentityProvider } from '../../../api/users'; | |||||
import { getBaseUrl } from '../../../helpers/urls'; | |||||
/*:: | |||||
type Props = { | |||||
formatLabel?: string => string, | |||||
identityProviders: Array<{ | |||||
backgroundColor: string, | |||||
iconPath: string, | |||||
key: string, | |||||
name: string | |||||
}> | |||||
}; | |||||
*/ | |||||
interface Props { | |||||
formatLabel?: (name: string) => string; | |||||
identityProviders: IdentityProvider[]; | |||||
} | |||||
export default function OAuthProviders(props /*: Props */) { | |||||
export default function OAuthProviders(props: Props) { | |||||
const formatLabel = props.formatLabel || defaultFormatLabel; | |||||
return ( | return ( | ||||
<section className="oauth-providers"> | <section className="oauth-providers"> | ||||
<ul> | <ul> | ||||
{props.identityProviders.map(identityProvider => ( | {props.identityProviders.map(identityProvider => ( | ||||
<li key={identityProvider.key}> | <li key={identityProvider.key}> | ||||
<a | <a | ||||
href={`${window.baseUrl}/sessions/init/${identityProvider.key}`} | |||||
href={`${getBaseUrl()}/sessions/init/${identityProvider.key}`} | |||||
style={{ backgroundColor: identityProvider.backgroundColor }} | style={{ backgroundColor: identityProvider.backgroundColor }} | ||||
// $FlowFixMe formatLabel is always defined through defaultProps | |||||
title={props.formatLabel(identityProvider.name)}> | |||||
title={formatLabel(identityProvider.name)}> | |||||
<img | <img | ||||
alt={identityProvider.name} | alt={identityProvider.name} | ||||
width="20" | width="20" | ||||
height="20" | height="20" | ||||
src={window.baseUrl + identityProvider.iconPath} | |||||
src={getBaseUrl() + identityProvider.iconPath} | |||||
/> | /> | ||||
<span> | |||||
{/* $FlowFixMe formatLabel is always defined through defaultProps */} | |||||
{props.formatLabel(identityProvider.name)} | |||||
</span> | |||||
<span>{formatLabel(identityProvider.name)}</span> | |||||
</a> | </a> | ||||
</li> | </li> | ||||
))} | ))} | ||||
); | ); | ||||
} | } | ||||
OAuthProviders.defaultProps = { | |||||
formatLabel: (name /*: string */) => translateWithParameters('login.login_with_x', name) | |||||
}; | |||||
function defaultFormatLabel(name: string) { | |||||
return translateWithParameters('login.login_with_x', name); | |||||
} |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import SimpleContainer from '../../../app/components/SimpleContainer'; | import SimpleContainer from '../../../app/components/SimpleContainer'; | ||||
/*:: | |||||
type Props = { | |||||
children?: React.Element<*> | Array<React.Element<*>> | |||||
}; | |||||
*/ | |||||
interface Props { | |||||
children?: React.ReactElement<any>; | |||||
} | |||||
export default function SimpleSessionsContainer({ children } /*: Props */) { | |||||
export default function SimpleSessionsContainer({ children }: Props) { | |||||
return <SimpleContainer hideLoggedInInfo={true}>{children}</SimpleContainer>; | return <SimpleContainer hideLoggedInInfo={true}>{children}</SimpleContainer>; | ||||
} | } |
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { Link } from 'react-router'; | import { Link } from 'react-router'; | ||||
import { translate } from '../../../helpers/l10n'; | |||||
interface Props { | interface Props { | ||||
location: { | location: { | ||||
return ( | return ( | ||||
<div className="text-center"> | <div className="text-center"> | ||||
<p id="unauthorized"> | |||||
{"You're not authorized to access this page. Please contact the administrator."} | |||||
</p> | |||||
<p id="unauthorized">{translate('unauthorized.message')}</p> | |||||
{!!message && <p className="spacer-top">Reason : {message}</p>} | |||||
{!!message && ( | |||||
<p className="spacer-top"> | |||||
{translate('unauthorized.reason')} {message} | |||||
</p> | |||||
)} | |||||
<div className="big-spacer-top"> | <div className="big-spacer-top"> | ||||
<Link to="/">Home</Link> | |||||
<Link to="/">{translate('layout.home')}</Link> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
); | ); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
// @flow | |||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import LoginForm from '../LoginForm'; | import LoginForm from '../LoginForm'; | ||||
import { change, click, submit } from '../../../../helpers/testUtils'; | import { change, click, submit } from '../../../../helpers/testUtils'; |
login.login_to_sonarqube | login.login_to_sonarqube | ||||
</h1> | </h1> | ||||
<OAuthProviders | <OAuthProviders | ||||
formatLabel={[Function]} | |||||
identityProviders={ | identityProviders={ | ||||
Array [ | Array [ | ||||
Object { | Object { | ||||
login.login_to_sonarqube | login.login_to_sonarqube | ||||
</h1> | </h1> | ||||
<OAuthProviders | <OAuthProviders | ||||
formatLabel={[Function]} | |||||
identityProviders={ | identityProviders={ | ||||
Array [ | Array [ | ||||
Object { | Object { | ||||
autoFocus={true} | autoFocus={true} | ||||
className="login-input" | className="login-input" | ||||
id="login" | id="login" | ||||
maxLength="255" | |||||
maxLength={255} | |||||
name="login" | name="login" | ||||
onChange={[Function]} | onChange={[Function]} | ||||
placeholder="login" | placeholder="login" | ||||
> | > | ||||
sessions.log_in | sessions.log_in | ||||
</button> | </button> | ||||
<a | |||||
<Link | |||||
className="spacer-left" | className="spacer-left" | ||||
href="/" | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to="/" | |||||
> | > | ||||
cancel | cancel | ||||
</a> | |||||
</Link> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
login.login_to_sonarqube | login.login_to_sonarqube | ||||
</h1> | </h1> | ||||
<OAuthProviders | <OAuthProviders | ||||
formatLabel={[Function]} | |||||
identityProviders={ | identityProviders={ | ||||
Array [ | Array [ | ||||
Object { | Object { | ||||
autoFocus={true} | autoFocus={true} | ||||
className="login-input" | className="login-input" | ||||
id="login" | id="login" | ||||
maxLength="255" | |||||
maxLength={255} | |||||
name="login" | name="login" | ||||
onChange={[Function]} | onChange={[Function]} | ||||
placeholder="login" | placeholder="login" | ||||
> | > | ||||
sessions.log_in | sessions.log_in | ||||
</button> | </button> | ||||
<a | |||||
<Link | |||||
className="spacer-left" | className="spacer-left" | ||||
href="/" | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to="/" | |||||
> | > | ||||
cancel | cancel | ||||
</a> | |||||
</Link> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</form> | </form> |
login.more_options=More options | login.more_options=More options | ||||
login.login_with_x=Log in with {0} | login.login_with_x=Log in with {0} | ||||
unauthorized.message=You're not authorized to access this page. Please contact the administrator. | |||||
unauthorized.reason=Reason: | |||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
# | # | ||||
# USERS & GROUPS PAGE | # USERS & GROUPS PAGE |
<tr> | <tr> | ||||
<td>assertText</td> | <td>assertText</td> | ||||
<td>bd</td> | <td>bd</td> | ||||
<td>*Reason : A functional error has happened*</td> | |||||
<td>*Reason: A functional error has happened*</td> | |||||
</tr> | </tr> | ||||
</tbody> | </tbody> | ||||
</table> | </table> |
<tr> | <tr> | ||||
<td>waitForText</td> | <td>waitForText</td> | ||||
<td>bd</td> | <td>bd</td> | ||||
<td>*You're not authorized to access this page. Please contact the administrator.*Reason : 'fake-base-id-provider' users are not allowed to sign up*</td> | |||||
<td>*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-base-id-provider' users are not allowed to sign up*</td> | |||||
</tr> | </tr> | ||||
</tbody> | </tbody> | ||||
</table> | </table> |
<tr> | <tr> | ||||
<td>assertText</td> | <td>assertText</td> | ||||
<td>bd</td> | <td>bd</td> | ||||
<td>*Reason : A functional error has happened*</td> | |||||
<td>*Reason: A functional error has happened*</td> | |||||
</tr> | </tr> | ||||
</tbody> | </tbody> | ||||
</table> | </table> |
<tr> | <tr> | ||||
<td>waitForText</td> | <td>waitForText</td> | ||||
<td>bd</td> | <td>bd</td> | ||||
<td>*You're not authorized to access this page. Please contact the administrator.*Reason : 'fake-oauth2-id-provider' users are not allowed to sign up*</td> | |||||
<td>*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-oauth2-id-provider' users are not allowed to sign up*</td> | |||||
</tr> | </tr> | ||||
</tbody> | </tbody> | ||||
</table> | </table> |