import React, { useState, useEffect } from "react"; import { FormBuilder, Icon, Input, Alert, Loader } from "../../components/"; import { Backend, Config, Middleware } from "../../model/"; import { FormObjToJSON, notify, format, createFormBackend, objectGet, JSONStringify } from "../../helpers/"; import { t } from "../../locales/"; import "./backend.scss"; export class BackendPage extends React.Component { constructor(props) { super(props); this.state = { backend_enabled: [], backend_available: {}, auth_enabled: null, auth_available: {}, config: null, isLoading: true, }; } componentDidMount() { Promise.all([ Backend.all(), Config.all(), Middleware.getAllAuthentication(), ]).then((data) => { const [backend, config, middleware_auth] = data; delete config["constants"]; this.setState({ isLoading: false, backend_available: backend, backend_enabled: window.CONFIG["connections"].filter((b) => b).map((conn) => { const f = createFormBackend(backend, conn); if (Object.keys(f).length === 0) { return null; } return f; }).filter((a) => a !== null), config: config, auth_available: middleware_auth, auth_enabled: { // We are storing the config in a fixed schema as we had issues with handling // different schema for each authentication middleware. "identity_provider": (function() { let { type, params } = objectGet(config, ["middleware", "identity_provider"]) || {}; type = objectGet(type, ["value"]); params = objectGet(params, ["value"]); if (!type) return {}; const idpParams = JSON.parse(params); const idpForm = middleware_auth[type] || {}; let key = null; for (key in idpParams) { if (!idpForm[key]) continue; idpForm[key]["value"] = idpParams[key]; } return idpForm; }()), "attribute_mapping": (function(state) { let { related_backend, params = {} } = objectGet(config, ["middleware", "attribute_mapping"]) || {}; related_backend = objectGet(related_backend, ["value"]); params = JSON.parse(objectGet(params, ["value"]) || "{}"); const backendsForm = Object.keys(params).reduce((acc, key) => { const t = createFormBackend( backend, params[key], ); acc[key] = t[params[key]["type"]]; return acc; }, {}); return { "related_backend": { "label": "Related Backend", "type": "text", "description": "List of backends to have behind the authentication process. Can be either a backend type of the actual label", "placeholder": "eg: ftp,sftp,webdav", "readonly": false, "default": null, "value": related_backend, "multi": true, "datalist": window.CONFIG["connections"].map((r) => r.label), "required": true, }, ...backendsForm, }; })(), }, }); }); } componentWillUnmount() { this.props.isSaving(false); Config.clear(); } refresh() { // refresh the screen to refresh the mutation // that have happenned down the stack which react couldn't detect directly this.setState({ refresh: Math.random() }); } _buildConfig() { const json = FormObjToJSON(this.state.config); json.connections = this.state.backend_enabled.map((backend) => { const data = FormObjToJSON(backend, (obj, key) => { if (obj[key].enabled === true) { obj[key] = obj[key].value || obj[key].default; } else { delete obj[key]; } }); const key = Object.keys(data)[0]; return data[key]; }); return json; } onUpdateStorageBackend(e) { this.refresh(); const json = this._buildConfig(); this.props.isSaving(true); return Config.save(json, true, () => { this.props.isSaving(false); }, (err) => { notify.send(err && err.message || t("Oops"), "error"); this.props.isSaving(false); }); } onUpdateAuthenticationMiddleware(middlewareData = null) { this.refresh(); const json = this._buildConfig(); json["middleware"] = { "identity_provider": (function() { const { type, ...other } = objectGet(middlewareData, ["identity_provider"]) || {}; return { "type": type || null, "params": JSONStringify(other), }; })(), "attribute_mapping": (function() { let { related_backend = null, ...params } = objectGet(middlewareData, ["attribute_mapping"]) || {}; const obj = { "related_backend": related_backend || "nop" }; if(Object.keys(params).length > 0) { obj.params = JSONStringify(params); } return obj; })(), }; this.props.isSaving(true); return Config.save(json, true, () => { this.props.isSaving(false); }, (err) => { notify.send(err && err.message || t("Oops"), "error"); this.props.isSaving(false); }); } addBackend(backend_id) { this.setState({ backend_enabled: this.state.backend_enabled.concat( createFormBackend(this.state.backend_available, { type: backend_id, label: function() { const existingLabels = this.state.backend_enabled .filter((b) => !!b[backend_id]) .map((b) => b[backend_id]["label"]["value"]); for (let i=1; i<=existingLabels.length; i++) { const desiredLabel = backend_id.toUpperCase() + i; if (existingLabels.indexOf(desiredLabel) === -1) { return desiredLabel; } } return backend_id.toUpperCase(); }.bind(this)(), }), ), }, this.onUpdateStorageBackend.bind(this)); } removeBackend(n) { this.setState({ backend_enabled: this.state.backend_enabled.filter((_, i) => i !== n), }, this.onUpdateStorageBackend.bind(this)); } changeAuthentication(auth) { this.setState({ auth_enabled: { "identity_provider": auth === null ? {} : this.state.auth_available[auth], "attribute_mapping": objectGet(this.state.auth_enabled, ["attribute_mapping"]) || {}, }, }, () => { this.onUpdateAuthenticationMiddleware(FormObjToJSON(this.state.auth_enabled)); }); } render() { const formRender = ($input, props, struct, onChange) => { if (struct.type === "enable") { // toggle buttons is to hide info from the login page // in this screen, we don't want users to have to click through too many things return null; } return ( ); }; return (
{ this.state.isLoading ? ( ) : (
Once you have selected one or more storage backend, you can add some middleware to:
  1. link the storage to an identity provider using an authentication middleware plugin
  2. change who can do what and where using an authorisation middleware plugin

ref: plugin documentation
) }
); } } function StorageBackend({ backend_available, backend_enabled, backend_add, backend_remove, formChange, formRender }) { const isActiveBackend = (backend_key) => { return backend_enabled .map((b) => Object.keys(b)[0]) .indexOf(backend_key) !== -1; }; return (

Storage Backend

{ Object.keys(backend_available) .sort((a, b) => a > b) .map((backend_available_current, index) => (
backend_add(backend_available_current)} className={"box-item pointer no-select" + (isActiveBackend(backend_available_current) ? " active": "")}>
{ backend_available_current } +
)) }
{ backend_enabled.length !== 0 ? (
{ backend_enabled.map((backend_enabled_current, index) => { return (
backend_remove(index)}>
); }) }
) : There is no storage selected. Where do you want to connect to? }
); } function AuthenticationMiddleware({ authentication_available, authentication_enabled, backend_available, backend_enabled, authentication_update, formChange, formRender }) { const [formSpec, setFormSpec] = useState(authentication_enabled); const formChangeHandler = (e) => { formChange(e[""]); }; useEffect(() => { setFormSpec(authentication_enabled); }, [authentication_enabled]); // we want to update the form in a few scenarios: // 1. user remove a storage backend // 2. user add a storage backend // 3. add a related backend in attribute mapping // 4. remove a related backend in attribute mapping // we want to update the form whenever a user change the related_backend input. // The change could be to either: // 1. add something to the list => create a new form in the attribute_mapping section // 2. remove something from the list => remove something in the attribute_mapping section useEffect(() => { if (!formSpec["identity_provider"]) return; const existingValues = (formSpec["attribute_mapping"]["related_backend"]["value"] || "") .split(/, ?/) .map((a) => a.trim()); const { identity_provider, attribute_mapping } = formSpec; const selected = backend_enabled.map((b) => { const type = Object.keys(b)[0]; return { label: b[type].label.value, type: Object.keys(b)[0], }; }); let needToSave = false; // detect missing form from the existing attribute_mapping // this happen whenever a user added something in the related_backend input for (let i=0; i { if (key === "related_backend") return; if (existingValues.indexOf(key) !== -1) return; needToSave = true; delete attribute_mapping[key]; }); if (needToSave === false) return; const d = { identity_provider, attribute_mapping: attribute_mapping, }; formChange(FormObjToJSON(d)); setFormSpec(d); }, [ formSpec["attribute_mapping"]["related_backend"]["value"], !formSpec["identity_provider"], ]); useEffect(() => { // autocompletion of the related_backend field const f = { ...formSpec }; f.attribute_mapping.related_backend.datalist = backend_enabled .map((r) => r[Object.keys(r)[0]].label.value); const enabledBackendLabel = backend_enabled.map((b) => b[Object.keys(b)[0]].label.value); f.attribute_mapping.related_backend.value = (f.attribute_mapping.related_backend.value || "") .split(/, ?/) .filter((value) => enabledBackendLabel.indexOf(value) !== -1) .join(", "); setFormSpec(f); }, [backend_enabled]); const isActiveAuth = (auth_key) => { return auth_key === objectGet(authentication_enabled, ["identity_provider", "type", "value"]); }; if (Object.keys(authentication_available).length === 0) return null; return (

Authentication Middleware

{ Object.keys(authentication_available) .map((auth_current) => (
authentication_update(isActiveAuth(auth_current) ? null : auth_current)} className={"box-item pointer no-select" + (isActiveAuth(auth_current) ? " active": "")}>
{ auth_current } { isActiveAuth(auth_current) === false ? "+" : }
)) }
{ objectGet(authentication_enabled, ["identity_provider", "type"]) && (
) }
); }