Skip to content

Commit

Permalink
Limit /api/server information and add authentication requirement for …
Browse files Browse the repository at this point in the history
…/api/docs (#352)
  • Loading branch information
psrok1 committed Apr 21, 2021
1 parent 8af3dea commit 93fb549
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 28 deletions.
8 changes: 7 additions & 1 deletion mwdb/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@
RemoteTextBlobPushResource,
)
from mwdb.resources.search import SearchResource
from mwdb.resources.server import PingResource, ServerDocsResource, ServerInfoResource
from mwdb.resources.server import (
PingResource,
ServerDocsResource,
ServerInfoResource,
ServerPluginInfoResource,
)
from mwdb.resources.share import ShareGroupListResource, ShareResource
from mwdb.resources.tag import TagListResource, TagResource
from mwdb.resources.user import (
Expand Down Expand Up @@ -195,6 +200,7 @@ def require_auth():
# Server health endpoints
api.add_resource(PingResource, "/ping")
api.add_resource(ServerInfoResource, "/server")
api.add_resource(ServerPluginInfoResource, "/server/plugins")
api.add_resource(ServerDocsResource, "/docs")

# Authentication endpoints
Expand Down
36 changes: 32 additions & 4 deletions mwdb/resources/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@
from mwdb.core.app import api
from mwdb.core.config import app_config
from mwdb.core.plugins import get_plugin_info
from mwdb.schema.server import ServerInfoResponseSchema, ServerPingResponseSchema
from mwdb.schema.server import (
ServerInfoResponseSchema,
ServerPingResponseSchema,
ServerPluginInfoResponseSchema,
)
from mwdb.version import app_build_version

from . import requires_authorization


class PingResource(Resource):
def get(self):
Expand Down Expand Up @@ -52,19 +58,41 @@ def get(self):
"is_maintenance_set": app_config.mwdb.enable_maintenance,
"is_registration_enabled": app_config.mwdb.enable_registration,
"recaptcha_site_key": app_config.mwdb.recaptcha_site_key,
"base_url": app_config.mwdb.base_url,
"active_plugins": get_plugin_info(),
"remotes": app_config.mwdb.remotes,
}
)


class ServerPluginInfoResource(Resource):
@requires_authorization
def get(self):
"""
---
summary: Get plugin information
description: Returns information about installed plugins
security:
- bearerAuth: []
tags:
- server
responses:
200:
description: Server plugin info with public configuration
content:
application/json:
schema: ServerInfoResponseSchema
"""
schema = ServerPluginInfoResponseSchema()
return schema.dump({"active_plugins": get_plugin_info()})


class ServerDocsResource(Resource):
@requires_authorization
def get(self):
"""
---
summary: Get server API documentation
description: Returns API documentation in OAS3 format
security:
- bearerAuth: []
tags:
- server
responses:
Expand Down
6 changes: 3 additions & 3 deletions mwdb/schema/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ServerInfoResponseSchema(Schema):
is_maintenance_set = fields.Boolean(required=True, allow_none=False)
is_registration_enabled = fields.Boolean(required=True, allow_none=False)
recaptcha_site_key = fields.Str(required=True, allow_none=True)
base_url = fields.Str(required=True, allow_none=False)
# Dict() supporting keys and values is added to marshmallow 3.x


class ServerPluginInfoResponseSchema(Schema):
active_plugins = fields.Dict(required=True, allow_none=False)
remotes = fields.List(fields.Str(), required=True, allow_none=False)
2 changes: 1 addition & 1 deletion mwdb/web/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default function App() {
const auth = useContext(AuthContext);
const config = useContext(ConfigContext);

const routeSwitch = config.config ? (
const routeSwitch = config.isReady ? (
<Switch>
<Route exact path="/login">
<UserLogin />
Expand Down
5 changes: 5 additions & 0 deletions mwdb/web/src/commons/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ function getServerInfo() {
return axios.get("/server");
}

function getServerPluginInfo() {
return axios.get("/server/plugins");
}

function authLogin(login, password) {
return axios.post("/auth/login", { login, password });
}
Expand Down Expand Up @@ -442,6 +446,7 @@ export default {
getApiForEnvironment,
getServerDocs,
getServerInfo,
getServerPluginInfo,
authLogin,
authGroups,
authRefresh,
Expand Down
41 changes: 34 additions & 7 deletions mwdb/web/src/commons/config/provider.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import React, { useEffect, useReducer } from "react";
import React, { useContext, useEffect, useReducer } from "react";
import api from "../api";
import { ConfigContext } from "./context";
import { AuthContext } from "../auth";

const configUpdate = Symbol("configUpdate");
const configError = Symbol("configError");

function serverConfigReducer(state, action) {
switch (action.type) {
case configUpdate:
return { config: action.config, error: null };
return {
config: { ...state.config, ...action.config },
error: null,
};
case configError:
return { config: null, error: action.error };
return { config: state.config, error: action.error };
default:
return state;
}
}

export function ConfigProvider(props) {
const auth = useContext(AuthContext);
const [serverConfig, setServerConfig] = useReducer(serverConfigReducer, {
config: null,
config: {},
error: null,
});

async function update() {
async function updateServerInfo() {
try {
const response = await api.getServerInfo();
setServerConfig({
Expand All @@ -37,16 +42,38 @@ export function ConfigProvider(props) {
}
}

async function updateRemoteInfo() {
try {
const response = await api.getRemoteNames();
setServerConfig({
type: configUpdate,
config: {
remotes: response.data.remotes,
},
});
} catch (error) {
setServerConfig({
type: configError,
error,
});
}
}

useEffect(() => {
update();
updateServerInfo();
}, []);

useEffect(() => {
if (auth.isAuthenticated) updateRemoteInfo();
}, [auth.isAuthenticated]);

return (
<ConfigContext.Provider
value={{
config: serverConfig.config,
configError: serverConfig.error,
update,
isReady: !!serverConfig.config["server_version"],
update: updateServerInfo,
}}
>
{props.children}
Expand Down
28 changes: 24 additions & 4 deletions mwdb/web/src/components/About.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useContext } from "react";
import React, { useCallback, useContext, useEffect, useState } from "react";

import api from "@mwdb-web/commons/api";
import { ConfigContext } from "@mwdb-web/commons/config";
import { View } from "@mwdb-web/commons/ui";

Expand Down Expand Up @@ -27,9 +28,28 @@ function PluginItems(props) {

export default function About() {
const config = useContext(ConfigContext);
let plugins = Object.entries(config.config["active_plugins"])
.sort()
.map(([key, value]) => <PluginItems name={key} info={value} />);
const [pluginsList, setPluginsList] = useState([]);

async function updatePlugins() {
try {
const response = await api.getServerPluginInfo();
setPluginsList(
Object.entries(response.data["active_plugins"]).sort()
);
} catch (e) {
console.error(e);
}
}

const getPlugins = useCallback(updatePlugins, []);

useEffect(() => {
getPlugins();
}, [getPlugins]);

const plugins = pluginsList.map(([key, value]) => (
<PluginItems name={key} info={value} />
));

return (
<div className="align-items-center">
Expand Down
9 changes: 5 additions & 4 deletions mwdb/web/src/components/Navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ function AdminDropdown() {

function RemoteDropdown() {
const config = useContext(ConfigContext);
if (!config.config) return [];
if (!config.isReady) return [];

const remoteItems = config.config.remotes.map((remote) => (
const remotes = config.config.remotes || [];
const remoteItems = remotes.map((remote) => (
<Link key="remote" className="dropdown-item" to={`/remote/${remote}`}>
{remote}
</Link>
Expand Down Expand Up @@ -141,7 +142,7 @@ export default function Navigation() {
const config = useContext(ConfigContext);
const remote = useRemote();
const remotePath = useRemotePath();
const navItems = config.config ? (
const navItems = config.isReady ? (
<Extendable ident="navbar">
{!auth.isAuthenticated &&
config.config["is_registration_enabled"] ? (
Expand Down Expand Up @@ -242,7 +243,7 @@ export default function Navigation() {
);

const remoteNavItems =
config.config && auth.isAuthenticated ? (
config.isReady && auth.isAuthenticated ? (
<Extendable ident="navbarAuthenticated">
<React.Fragment>
<li className="nav-item">
Expand Down
8 changes: 4 additions & 4 deletions mwdb/web/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import "swagger-ui-react/swagger-ui.css";

ReactDOM.render(
<BrowserRouter>
<ConfigProvider>
<AuthProvider>
<AuthProvider>
<ConfigProvider>
<APIProvider>
<App />
</APIProvider>
</AuthProvider>
</ConfigProvider>
</ConfigProvider>
</AuthProvider>
</BrowserRouter>,
document.getElementById("root")
);

0 comments on commit 93fb549

Please sign in to comment.