Skip to content

Commit

Permalink
Reset users' startpages if referenced stream/dashboard is deleted. (#…
Browse files Browse the repository at this point in the history
…2702)

* Add Listener to reset users' startpages if stream/dashboard is deleted.

This change adds a listener and the necessary events, which are posted
when a stream or dashboard is deleted. The listener will then reset any
user's startpage if it references the deleted entity.

* Adding licenses.

* Refresh current user before redirecting to start page.

* Reset currentUser on logout.

* Check presence of currentUser first.

* Switching back to Reflux.connect because initial state is correct now.

* Checking for presence of currentUser too, because it can be undefined.
  • Loading branch information
dennisoelkers authored and edmundoa committed Aug 17, 2016
1 parent d9de776 commit f167fb0
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 24 deletions.
Expand Up @@ -89,6 +89,7 @@
import org.graylog2.system.stats.ClusterStatsModule; import org.graylog2.system.stats.ClusterStatsModule;
import org.graylog2.users.RoleService; import org.graylog2.users.RoleService;
import org.graylog2.users.RoleServiceImpl; import org.graylog2.users.RoleServiceImpl;
import org.graylog2.users.StartPageCleanupListener;
import org.graylog2.users.UserImpl; import org.graylog2.users.UserImpl;


import javax.ws.rs.container.DynamicFeature; import javax.ws.rs.container.DynamicFeature;
Expand Down Expand Up @@ -207,6 +208,7 @@ private void bindEventBusListeners() {
bind(InputEventListener.class).asEagerSingleton(); bind(InputEventListener.class).asEagerSingleton();
bind(LocalDebugEventListener.class).asEagerSingleton(); bind(LocalDebugEventListener.class).asEagerSingleton();
bind(ClusterDebugEventListener.class).asEagerSingleton(); bind(ClusterDebugEventListener.class).asEagerSingleton();
bind(StartPageCleanupListener.class).asEagerSingleton();
} }


private void bindSearchResponseDecorators() { private void bindSearchResponseDecorators() {
Expand Down
@@ -0,0 +1,36 @@
/**
* This file is part of Graylog.
*
* Graylog is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Graylog is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Graylog. If not, see <http://www.gnu.org/licenses/>.
*/
package org.graylog2.dashboards.events;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;

@AutoValue
@JsonAutoDetect
public abstract class DashboardDeletedEvent {
private static final String FIELD_DASHBOARD_ID = "dashboard_id";

@JsonProperty(FIELD_DASHBOARD_ID)
public abstract String dashboardId();

@JsonCreator
public static DashboardDeletedEvent create(@JsonProperty(FIELD_DASHBOARD_ID) String dashboardId) {
return new AutoValue_DashboardDeletedEvent(dashboardId);
}
}
Expand Up @@ -17,6 +17,7 @@
package org.graylog2.plugin.database.users; package org.graylog2.plugin.database.users;


import org.graylog2.plugin.database.Persisted; import org.graylog2.plugin.database.Persisted;
import org.graylog2.rest.models.users.requests.Startpage;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;


import javax.annotation.Nonnull; import javax.annotation.Nonnull;
Expand Down Expand Up @@ -47,7 +48,7 @@ public interface User extends Persisted {


Map<String, Object> getPreferences(); Map<String, Object> getPreferences();


Map<String, String> getStartpage(); Startpage getStartpage();


long getSessionTimeoutMs(); long getSessionTimeoutMs();


Expand Down Expand Up @@ -79,6 +80,8 @@ public interface User extends Persisted {


void setStartpage(String type, String id); void setStartpage(String type, String id);


void setStartpage(Startpage startpage);

boolean isLocalAdmin(); boolean isLocalAdmin();


@Nonnull @Nonnull
Expand Down
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import org.graylog2.rest.models.users.requests.Startpage;


import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Date; import java.util.Date;
Expand Down Expand Up @@ -67,7 +68,7 @@ public abstract class UserSummary {


@JsonProperty @JsonProperty
@Nullable @Nullable
public abstract Map<String, String> startpage(); public abstract Startpage startpage();


@JsonProperty @JsonProperty
@Nullable @Nullable
Expand Down Expand Up @@ -95,7 +96,7 @@ public static UserSummary create(@JsonProperty("id") @Nullable String id,
@JsonProperty("session_timeout_ms") @Nullable Long sessionTimeoutMs, @JsonProperty("session_timeout_ms") @Nullable Long sessionTimeoutMs,
@JsonProperty("read_only") boolean readOnly, @JsonProperty("read_only") boolean readOnly,
@JsonProperty("external") boolean external, @JsonProperty("external") boolean external,
@JsonProperty("startpage") @Nullable Map<String, String> startpage, @JsonProperty("startpage") @Nullable Startpage startpage,
@JsonProperty("roles") @Nullable Set<String> roles, @JsonProperty("roles") @Nullable Set<String> roles,
@JsonProperty("session_active") boolean sessionActive, @JsonProperty("session_active") boolean sessionActive,
@JsonProperty("last_activity") @Nullable Date lastActivity, @JsonProperty("last_activity") @Nullable Date lastActivity,
Expand Down
Expand Up @@ -19,17 +19,18 @@
import com.codahale.metrics.annotation.Timed; import com.codahale.metrics.annotation.Timed;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses; import io.swagger.annotations.ApiResponses;
import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.graylog2.auditlog.Actions;
import org.graylog2.auditlog.jersey.AuditLog; import org.graylog2.auditlog.jersey.AuditLog;
import org.graylog2.dashboards.Dashboard; import org.graylog2.dashboards.Dashboard;
import org.graylog2.dashboards.DashboardService; import org.graylog2.dashboards.DashboardService;
import org.graylog2.dashboards.events.DashboardDeletedEvent;
import org.graylog2.dashboards.widgets.WidgetResultCache; import org.graylog2.dashboards.widgets.WidgetResultCache;
import org.graylog2.database.NotFoundException; import org.graylog2.database.NotFoundException;
import org.graylog2.plugin.Tools; import org.graylog2.plugin.Tools;
Expand Down Expand Up @@ -70,14 +71,17 @@ public class DashboardsResource extends RestResource {
private final DashboardService dashboardService; private final DashboardService dashboardService;
private final ActivityWriter activityWriter; private final ActivityWriter activityWriter;
private final WidgetResultCache widgetResultCache; private final WidgetResultCache widgetResultCache;
private final EventBus serverEventBus;


@Inject @Inject
public DashboardsResource(DashboardService dashboardService, public DashboardsResource(DashboardService dashboardService,
ActivityWriter activityWriter, ActivityWriter activityWriter,
WidgetResultCache widgetResultCache) { WidgetResultCache widgetResultCache,
EventBus serverEventBus) {
this.dashboardService = dashboardService; this.dashboardService = dashboardService;
this.activityWriter = activityWriter; this.activityWriter = activityWriter;
this.widgetResultCache = widgetResultCache; this.widgetResultCache = widgetResultCache;
this.serverEventBus = serverEventBus;
} }


@POST @POST
Expand Down Expand Up @@ -149,6 +153,8 @@ public void delete(@ApiParam(name = "dashboardId", required = true)
final String msg = "Deleted dashboard <" + dashboard.getId() + ">. Reason: REST request."; final String msg = "Deleted dashboard <" + dashboard.getId() + ">. Reason: REST request.";
LOG.info(msg); LOG.info(msg);
activityWriter.write(new Activity(msg, DashboardsResource.class)); activityWriter.write(new Activity(msg, DashboardsResource.class));

this.serverEventBus.post(DashboardDeletedEvent.create(dashboard.getId()));
} }


@PUT @PUT
Expand Down
Expand Up @@ -62,6 +62,7 @@
import org.graylog2.streams.StreamRuleImpl; import org.graylog2.streams.StreamRuleImpl;
import org.graylog2.streams.StreamRuleService; import org.graylog2.streams.StreamRuleService;
import org.graylog2.streams.StreamService; import org.graylog2.streams.StreamService;
import org.graylog2.streams.events.StreamDeletedEvent;
import org.graylog2.streams.events.StreamsChangedEvent; import org.graylog2.streams.events.StreamsChangedEvent;
import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.NotEmpty;
import org.joda.time.DateTime; import org.joda.time.DateTime;
Expand Down Expand Up @@ -257,6 +258,7 @@ public void delete(@ApiParam(name = "streamId", required = true) @PathParam("str
final Stream stream = streamService.load(streamId); final Stream stream = streamService.load(streamId);
streamService.destroy(stream); streamService.destroy(stream);
clusterEventBus.post(StreamsChangedEvent.create(stream.getId())); clusterEventBus.post(StreamsChangedEvent.create(stream.getId()));
clusterEventBus.post(StreamDeletedEvent.create(stream.getId()));
} }


@POST @POST
Expand Down
@@ -0,0 +1,36 @@
/**
* This file is part of Graylog.
*
* Graylog is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Graylog is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Graylog. If not, see <http://www.gnu.org/licenses/>.
*/
package org.graylog2.streams.events;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;

@AutoValue
@JsonAutoDetect
public abstract class StreamDeletedEvent {
private static final String FIELD_STREAM_ID = "stream_id";

@JsonProperty(FIELD_STREAM_ID)
public abstract String streamId();

@JsonCreator
public static StreamDeletedEvent create(@JsonProperty(FIELD_STREAM_ID) String streamId) {
return new AutoValue_StreamDeletedEvent(streamId);
}
}
@@ -0,0 +1,70 @@
/**
* This file is part of Graylog.
*
* Graylog is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Graylog is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Graylog. If not, see <http://www.gnu.org/licenses/>.
*/
package org.graylog2.users;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.graylog2.dashboards.events.DashboardDeletedEvent;
import org.graylog2.plugin.database.ValidationException;
import org.graylog2.rest.models.users.requests.Startpage;
import org.graylog2.shared.users.UserService;
import org.graylog2.streams.events.StreamDeletedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;

public class StartPageCleanupListener {
private static final Logger LOG = LoggerFactory.getLogger(StartPageCleanupListener.class);

private final UserService userService;

@Inject
public StartPageCleanupListener(EventBus serverEventBus,
UserService userService) {
this.userService = userService;
serverEventBus.register(this);
}

@Subscribe
@SuppressWarnings("unused")
public void removeStartpageReferencesIfStreamDeleted(StreamDeletedEvent streamDeletedEvent) {
final Startpage deletedStartpage = Startpage.create("stream", streamDeletedEvent.streamId());
resetReferencesToStartpage(deletedStartpage);
}

@Subscribe
@SuppressWarnings("unused")
public void removeStartpageReferencesIfDashboardDeleted(DashboardDeletedEvent dashboardDeletedEvent) {
final Startpage deletedStartpage = Startpage.create("dashboard", dashboardDeletedEvent.dashboardId());
resetReferencesToStartpage(deletedStartpage);
}

private void resetReferencesToStartpage(Startpage deletedStartpage) {
this.userService.loadAll()
.stream()
.filter(user -> user.getStartpage() != null && user.getStartpage().equals(deletedStartpage))
.forEach(user -> {
user.setStartpage(null);
try {
this.userService.save(user);
} catch (ValidationException e) {
LOG.error("Unable to reset start page for user which references deleted start page: ", e);
}
});
}
}
25 changes: 14 additions & 11 deletions graylog2-server/src/main/java/org/graylog2/users/UserImpl.java
Expand Up @@ -35,6 +35,7 @@
import org.graylog2.plugin.database.users.User; import org.graylog2.plugin.database.users.User;
import org.graylog2.plugin.database.validators.Validator; import org.graylog2.plugin.database.validators.Validator;
import org.graylog2.plugin.security.PasswordAlgorithm; import org.graylog2.plugin.security.PasswordAlgorithm;
import org.graylog2.rest.models.users.requests.Startpage;
import org.graylog2.security.PasswordAlgorithmFactory; import org.graylog2.security.PasswordAlgorithmFactory;
import org.graylog2.shared.security.Permissions; import org.graylog2.shared.security.Permissions;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
Expand Down Expand Up @@ -194,22 +195,19 @@ public void setPreferences(final Map<String, Object> preferences) {
} }


@Override @Override
public Map<String, String> getStartpage() { public Startpage getStartpage() {
final Map<String, String> startpage = new HashMap<>();

if (fields.containsKey(STARTPAGE)) { if (fields.containsKey(STARTPAGE)) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Map<String, String> obj = (Map<String, String>) fields.get(STARTPAGE); final Map<String, String> obj = (Map<String, String>) fields.get(STARTPAGE);
final String type = obj.get("type"); final String type = obj.get("type");
final String id = obj.get("id"); final String id = obj.get("id");


if (type != null && id != null) { if (type != null && id != null) {
startpage.put("type", type); return Startpage.create(type, id);
startpage.put("id", id);
} }
} }


return startpage; return null;
} }


@Override @Override
Expand Down Expand Up @@ -317,14 +315,19 @@ public void setRoleIds(Set<String> roles) {


@Override @Override
public void setStartpage(final String type, final String id) { public void setStartpage(final String type, final String id) {
final Map<String, String> startpage = new HashMap<>();

if (type != null && id != null) { if (type != null && id != null) {
startpage.put("type", type); this.setStartpage(Startpage.create(type, id));
startpage.put("id", id);
} }
}


this.fields.put(STARTPAGE, startpage); @Override
public void setStartpage(Startpage startpage) {
final HashMap<String, String> startpageMap = new HashMap<>();
if (startpage != null) {
startpageMap.put("type", startpage.type());
startpageMap.put("id", startpage.id());
}
this.fields.put(STARTPAGE, startpageMap);
} }


public static class LocalAdminUser extends UserImpl { public static class LocalAdminUser extends UserImpl {
Expand Down
Expand Up @@ -29,7 +29,7 @@ const IfPermitted = React.createClass({
return this.isPermitted(this.state.currentUser.permissions, this.props.permissions); return this.isPermitted(this.state.currentUser.permissions, this.props.permissions);
}, },
render() { render() {
if (this._checkPermissions()) { if (this.state.currentUser && this._checkPermissions()) {
return React.Children.count(this.props.children) > 1 ? <span>{this.props.children}</span> : this.props.children; return React.Children.count(this.props.children) > 1 ? <span>{this.props.children}</span> : this.props.children;
} }


Expand Down
7 changes: 4 additions & 3 deletions graylog2-web-interface/src/pages/StartPage.jsx
@@ -1,4 +1,4 @@
import React, {PropTypes} from 'react'; import React from 'react';
import Reflux from 'reflux'; import Reflux from 'reflux';


import { Spinner } from 'components/common'; import { Spinner } from 'components/common';
Expand All @@ -15,7 +15,7 @@ const GettingStartedActions = ActionsProvider.getActions('GettingStarted');


const StartPage = React.createClass({ const StartPage = React.createClass({
propTypes: { propTypes: {
history: PropTypes.object.isRequired, history: React.PropTypes.object.isRequired,
}, },
mixins: [Reflux.connect(CurrentUserStore), Reflux.listenTo(GettingStartedStore, 'onGettingStartedUpdate')], mixins: [Reflux.connect(CurrentUserStore), Reflux.listenTo(GettingStartedStore, 'onGettingStartedUpdate')],
getInitialState() { getInitialState() {
Expand All @@ -25,14 +25,15 @@ const StartPage = React.createClass({
}, },
componentDidMount() { componentDidMount() {
GettingStartedActions.getStatus(); GettingStartedActions.getStatus();
CurrentUserStore.reload();
}, },
componentDidUpdate() { componentDidUpdate() {
if (!this._isLoading()) { if (!this._isLoading()) {
this._redirectToStartpage(); this._redirectToStartpage();
} }
}, },
onGettingStartedUpdate(state) { onGettingStartedUpdate(state) {
this.setState({gettingStarted: state.status}); this.setState({ gettingStarted: state.status });
}, },
_redirect(page) { _redirect(page) {
this.props.history.pushState(null, page); this.props.history.pushState(null, page);
Expand Down

0 comments on commit f167fb0

Please sign in to comment.