浏览代码

fix #920 Admin API: start/stop scheduled job

Shinsuke Sugaya 8 年之前
父节点
当前提交
e515dffdb1

+ 32 - 5
src/main/java/org/codelibs/fess/app/service/AccessTokenService.java

@@ -15,17 +15,26 @@
  */
 package org.codelibs.fess.app.service;
 
+import static org.codelibs.core.stream.StreamUtil.stream;
+
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
 
 import org.codelibs.core.beans.util.BeanUtil;
+import org.codelibs.core.lang.StringUtil;
 import org.codelibs.fess.Constants;
 import org.codelibs.fess.app.pager.AccessTokenPager;
 import org.codelibs.fess.es.config.cbean.AccessTokenCB;
 import org.codelibs.fess.es.config.exbhv.AccessTokenBhv;
 import org.codelibs.fess.es.config.exentity.AccessToken;
+import org.codelibs.fess.exception.InvalidAccessTokenException;
 import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.taglib.FessFunctions;
+import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.cbean.result.PagingResultBean;
 import org.dbflute.optional.OptionalEntity;
 
@@ -82,10 +91,28 @@ public class AccessTokenService {
 
     }
 
-    public OptionalEntity<AccessToken> getAccessTokenByToken(final String token) {
-        return accessTokenBhv.selectEntity(cb -> {
-            cb.query().setToken_Term(token);
-        });
+    public OptionalEntity<Set<String>> getPermissions(final HttpServletRequest request) {
+        final String token = request.getHeader("Authorization");
+        if (StringUtil.isNotBlank(token)) {
+            return accessTokenBhv
+                    .selectEntity(cb -> {
+                        cb.query().setToken_Term(token);
+                    })
+                    .map(accessToken -> {
+                        final Set<String> permissionSet = new HashSet<>();
+                        final Long expiredTime = accessToken.getExpiredTime();
+                        if (expiredTime != null && expiredTime.longValue() > 0
+                                && expiredTime.longValue() < ComponentUtil.getSystemHelper().getCurrentTimeAsLong()) {
+                            throw new InvalidAccessTokenException("invalid_token", "The token is expired("
+                                    + FessFunctions.formatDate(FessFunctions.date(expiredTime)) + ").");
+                        }
+                        stream(accessToken.getPermissions()).of(stream -> stream.forEach(permissionSet::add));
+                        final String name = accessToken.getParameterName();
+                        stream(request.getParameterValues(name)).of(
+                                stream -> stream.filter(StringUtil::isNotBlank).forEach(permissionSet::add));
+                        return OptionalEntity.of(permissionSet);
+                    }).orElseThrow(() -> new InvalidAccessTokenException("invalid_token", "Invalid token: " + token));
+        }
+        return OptionalEntity.empty();
     }
-
 }

+ 80 - 0
src/main/java/org/codelibs/fess/app/web/api/ApiResult.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012-2017 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api;
+
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.mylasta.action.FessMessages;
+import org.codelibs.fess.util.ComponentUtil;
+import org.lastaflute.web.util.LaRequestUtil;
+import org.lastaflute.web.validation.VaMessenger;
+
+public class ApiResult {
+
+    protected ApiResponse response = null;
+
+    public ApiResult(ApiResponse response) {
+        this.response = response;
+    }
+
+    public enum Status {
+        OK(0), BAD_REQUEST(1), SYSTEM_ERROR(2), UNAUTHORIZED(3);
+        private final int id;
+
+        private Status(final int id) {
+            this.id = id;
+        }
+
+        public int getId() {
+            return id;
+        }
+    }
+
+    public static class ApiResponse {
+        protected String version = Constants.WEB_API_VERSION;
+        protected int status;
+
+        public ApiResponse status(Status status) {
+            this.status = status.getId();
+            return this;
+        }
+
+        public ApiResult result() {
+            return new ApiResult(this);
+        }
+    }
+
+    public static class ApiErrorResponse extends ApiResponse {
+        protected String message;
+
+        public ApiErrorResponse message(String message) {
+            this.message = message;
+            return this;
+        }
+
+        public ApiErrorResponse message(VaMessenger<FessMessages> validationMessagesLambda) {
+            FessMessages messages = new FessMessages();
+            validationMessagesLambda.message(messages);
+            message =
+                    ComponentUtil.getMessageManager()
+                            .toMessageList(LaRequestUtil.getOptionalRequest().map(r -> r.getLocale()).orElse(Locale.ENGLISH), messages)
+                            .stream().collect(Collectors.joining(" "));
+            return this;
+        }
+    }
+}

+ 75 - 0
src/main/java/org/codelibs/fess/app/web/api/FessApiAction.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012-2017 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api;
+
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+import org.codelibs.fess.app.service.AccessTokenService;
+import org.codelibs.fess.app.web.api.ApiResult.ApiErrorResponse;
+import org.codelibs.fess.app.web.api.ApiResult.Status;
+import org.codelibs.fess.app.web.base.FessBaseAction;
+import org.codelibs.fess.mylasta.action.FessMessages;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.dbflute.optional.OptionalThing;
+import org.lastaflute.core.message.MessageManager;
+import org.lastaflute.web.login.LoginManager;
+import org.lastaflute.web.response.ActionResponse;
+import org.lastaflute.web.ruts.process.ActionRuntime;
+import org.lastaflute.web.validation.VaMessenger;
+
+public abstract class FessApiAction extends FessBaseAction {
+
+    @Resource
+    protected FessConfig fessConfig;
+
+    @Resource
+    protected MessageManager messageManager;
+
+    @Resource
+    protected AccessTokenService accessTokenService;
+
+    @Resource
+    protected HttpServletRequest request;
+
+    @Override
+    protected OptionalThing<LoginManager> myLoginManager() {
+        return OptionalThing.empty();
+    }
+
+    @Override
+    public ActionResponse godHandPrologue(final ActionRuntime runtime) {
+        if (!isAccessAllowed()) {
+            return asJson(new ApiErrorResponse().message(getMessage(messages -> messages.addErrorsUnauthorizedRequest(GLOBAL)))
+                    .status(Status.UNAUTHORIZED).result());
+        }
+        return super.godHandPrologue(runtime);
+    }
+
+    protected String getMessage(VaMessenger<FessMessages> validationMessagesLambda) {
+        final FessMessages messages = new FessMessages();
+        validationMessagesLambda.message(messages);
+        return messageManager.toMessageList(request.getLocale() == null ? Locale.ENGLISH : request.getLocale(), messages).stream()
+                .collect(Collectors.joining(" "));
+    }
+
+    protected boolean isAccessAllowed() {
+        return false;
+    }
+}

+ 40 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/FessApiAdminAction.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012-2017 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api.admin;
+
+import org.codelibs.fess.app.web.api.FessApiAction;
+import org.codelibs.fess.exception.InvalidAccessTokenException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class FessApiAdminAction extends FessApiAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(FessApiAdminAction.class);
+
+    @Override
+    protected boolean isAccessAllowed() {
+        try {
+            return accessTokenService.getPermissions(request).map(permissions -> {
+                return fessConfig.isApiAdminAccessAllowed(permissions);
+            }).orElse(false);
+        } catch (InvalidAccessTokenException e) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Invalid access token.", e);
+            }
+            return false;
+        }
+    }
+}

+ 82 - 0
src/main/java/org/codelibs/fess/app/web/api/admin/scheduler/ApiAdminSchedulerAction.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012-2017 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.api.admin.scheduler;
+
+import javax.annotation.Resource;
+
+import org.codelibs.fess.app.service.ScheduledJobService;
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.ApiResult.ApiResponse;
+import org.codelibs.fess.app.web.api.ApiResult.Status;
+import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.HtmlResponse;
+import org.lastaflute.web.response.JsonResponse;
+
+public class ApiAdminSchedulerAction extends FessApiAdminAction {
+
+    @Resource
+    private ScheduledJobService scheduledJobService;
+
+    @Execute
+    public HtmlResponse index() {
+        throw new UnsupportedOperationException();
+    }
+
+    // POST /api/admin/scheduler/{id}/start
+    @Execute(urlPattern = "{}/@word")
+    public JsonResponse<ApiResult> post$start(String id) {
+        scheduledJobService.getScheduledJob(id).ifPresent(entity -> {
+            if (!entity.isEnabled() || entity.isRunning()) {
+                throwValidationErrorApi(messages -> {
+                    messages.addErrorsFailedToStartJob(GLOBAL, entity.getName());
+                });
+            }
+            try {
+                entity.start();
+            } catch (final Exception e) {
+                throwValidationErrorApi(messages -> {
+                    messages.addErrorsFailedToStartJob(GLOBAL, entity.getName());
+                });
+            }
+        }).orElse(() -> {
+            throwValidationErrorApi(messages -> {
+                messages.addErrorsFailedToStartJob(GLOBAL, id);
+            });
+        });
+        return asJson(new ApiResponse().status(Status.OK).result());
+    }
+
+    // POST /api/admin/scheduler/{id}/stop
+    @Execute(urlPattern = "{}/@word")
+    public JsonResponse<ApiResult> post$stop(String id) {
+        scheduledJobService.getScheduledJob(id).ifPresent(entity -> {
+            try {
+                entity.stop();
+            } catch (final Exception e) {
+                throwValidationErrorApi(messages -> {
+                    messages.addErrorsFailedToStopJob(GLOBAL, entity.getName());
+                });
+            }
+        }).orElse(() -> {
+            throwValidationErrorApi(messages -> {
+                messages.addErrorsFailedToStartJob(GLOBAL, id);
+            });
+        });
+        return asJson(new ApiResponse().status(Status.OK).result());
+    }
+
+}

+ 1 - 23
src/main/java/org/codelibs/fess/helper/RoleQueryHelper.java

@@ -35,7 +35,6 @@ import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
 import org.codelibs.fess.exception.InvalidAccessTokenException;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.mylasta.direction.FessConfig;
-import org.codelibs.fess.taglib.FessFunctions;
 import org.codelibs.fess.util.ComponentUtil;
 import org.lastaflute.web.servlet.request.RequestManager;
 import org.lastaflute.web.util.LaRequestUtil;
@@ -158,28 +157,7 @@ public class RoleQueryHelper {
     }
 
     protected void processAccessToken(final HttpServletRequest request, final Set<String> roleSet) {
-        final String token = request.getHeader("Authorization");
-        if (StringUtil.isNotBlank(token)) {
-            final AccessTokenService accessTokenService = ComponentUtil.getComponent(AccessTokenService.class);
-            accessTokenService
-                    .getAccessTokenByToken(token)
-                    .ifPresent(
-                            accessToken -> {
-                                final Long expiredTime = accessToken.getExpiredTime();
-                                if (expiredTime != null && expiredTime.longValue() > 0
-                                        && expiredTime.longValue() < ComponentUtil.getSystemHelper().getCurrentTimeAsLong()) {
-                                    throw new InvalidAccessTokenException("invalid_token", "The token is expired("
-                                            + FessFunctions.formatDate(FessFunctions.date(expiredTime)) + ").");
-                                }
-                                stream(accessToken.getPermissions()).of(stream -> stream.forEach(roleSet::add));
-                                final String name = accessToken.getParameterName();
-                                stream(request.getParameterValues(name)).of(
-                                        stream -> stream.filter(StringUtil::isNotBlank).forEach(roleSet::add));
-                            }).orElse(() -> {
-                        throw new InvalidAccessTokenException("invalid_token", "Invalid token: " + token);
-                    });
-        }
-
+        ComponentUtil.getComponent(AccessTokenService.class).getPermissions(request).ifPresent(p -> p.forEach(roleSet::add));
     }
 
     protected String getAccessToken(final HttpServletRequest request) {

+ 3 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java

@@ -387,6 +387,9 @@ public interface FessHtmlPath {
     /** The path of the HTML: /searchNoResult.jsp */
     HtmlNext path_SearchNoResultJsp = new HtmlNext("/searchNoResult.jsp");
 
+    /** The path of the HTML: /searchOptions.jsp */
+    HtmlNext path_SearchOptionsJsp = new HtmlNext("/searchOptions.jsp");
+
     /** The path of the HTML: /searchResults.jsp */
     HtmlNext path_SearchResultsJsp = new HtmlNext("/searchResults.jsp");
 }

+ 17 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java

@@ -323,6 +323,9 @@ public class FessMessages extends FessLabels {
     /** The key of the message: Could not delete logged in user. */
     public static final String ERRORS_could_not_delete_logged_in_user = "{errors.could_not_delete_logged_in_user}";
 
+    /** The key of the message: Unauthorized request. */
+    public static final String ERRORS_unauthorized_request = "{errors.unauthorized_request}";
+
     /** The key of the message: The given query has unknown condition. */
     public static final String ERRORS_invalid_query_unknown = "{errors.invalid_query_unknown}";
 
@@ -1886,6 +1889,20 @@ public class FessMessages extends FessLabels {
         return this;
     }
 
+    /**
+     * Add the created action message for the key 'errors.unauthorized_request' with parameters.
+     * <pre>
+     * message: Unauthorized request.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsUnauthorizedRequest(String property) {
+        assertPropertyNotNull(property);
+        add(property, new UserMessage(ERRORS_unauthorized_request));
+        return this;
+    }
+
     /**
      * Add the created action message for the key 'errors.invalid_query_unknown' with parameters.
      * <pre>

+ 14 - 0
src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java

@@ -133,6 +133,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. false */
     String API_ACCESS_TOKEN_REQUIRED = "api.access.token.required";
 
+    /** The key of the configuration. e.g. Radmin-api */
+    String API_ADMIN_ACCESS_PERMISSIONS = "api.admin.access.permissions";
+
     /** The key of the configuration. e.g. 50 */
     String CRAWLER_DOCUMENT_MAX_SITE_LENGTH = "crawler.document.max.site.length";
 
@@ -1402,6 +1405,13 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
     boolean isApiAccessTokenRequired();
 
+    /**
+     * Get the value for the key 'api.admin.access.permissions'. <br>
+     * The value is, e.g. Radmin-api <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getApiAdminAccessPermissions();
+
     /**
      * Get the value for the key 'crawler.document.max.site.length'. <br>
      * The value is, e.g. 50 <br>
@@ -4798,6 +4808,10 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return is(FessConfig.API_ACCESS_TOKEN_REQUIRED);
         }
 
+        public String getApiAdminAccessPermissions() {
+            return get(FessConfig.API_ADMIN_ACCESS_PERMISSIONS);
+        }
+
         public String getCrawlerDocumentMaxSiteLength() {
             return get(FessConfig.CRAWLER_DOCUMENT_MAX_SITE_LENGTH);
         }

+ 19 - 0
src/main/java/org/codelibs/fess/mylasta/direction/FessProp.java

@@ -58,6 +58,8 @@ import org.lastaflute.web.validation.theme.typed.LongTypeValidator;
 
 public interface FessProp {
 
+    public static final String API_ADMIN_ACCESS_PERMISSION_SET = "apiAdminAccessPermissionSet";
+
     public static final String CRAWLER_DOCUMENT_SPACE_CHARS = "crawlerDocumentSpaceChars";
 
     public static final String INDEX_ADMIN_ARRAY_FIELD_SET = "indexAdminArrayFieldSet";
@@ -1469,4 +1471,21 @@ public interface FessProp {
 
     }
 
+    String getApiAdminAccessPermissions();
+
+    public default Set<String> getApiAdminAccessPermissionSet() {
+        @SuppressWarnings("unchecked")
+        Set<String> fieldSet = (Set<String>) propMap.get(API_ADMIN_ACCESS_PERMISSION_SET);
+        if (fieldSet == null) {
+            fieldSet =
+                    split(getApiAdminAccessPermissions(), ",").get(
+                            stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).collect(Collectors.toSet()));
+            propMap.put(API_ADMIN_ACCESS_PERMISSION_SET, fieldSet);
+        }
+        return fieldSet;
+    }
+
+    public default boolean isApiAdminAccessAllowed(final Set<String> accessPermissions) {
+        return getApiAdminAccessPermissionSet().stream().anyMatch(s -> accessPermissions.contains(s));
+    }
 }

+ 18 - 17
src/main/java/org/codelibs/fess/mylasta/direction/sponsor/FessApiFailureHook.java

@@ -15,8 +15,11 @@
  */
 package org.codelibs.fess.mylasta.direction.sponsor;
 
-import java.util.List;
+import java.util.stream.Collectors;
 
+import org.codelibs.fess.app.web.api.ApiResult;
+import org.codelibs.fess.app.web.api.ApiResult.ApiErrorResponse;
+import org.codelibs.fess.app.web.api.ApiResult.Status;
 import org.dbflute.optional.OptionalThing;
 import org.lastaflute.web.api.ApiFailureHook;
 import org.lastaflute.web.api.ApiFailureResource;
@@ -40,18 +43,16 @@ public class FessApiFailureHook implements ApiFailureHook { // #change_it for ha
     //                                                                    ================
     @Override
     public ApiResponse handleValidationError(final ApiFailureResource resource) {
-        return asJson(createFailureBean(resource)).httpStatus(HTTP_BAD_REQUEST);
+        return asJson(createFailureBean(Status.BAD_REQUEST, createMessage(resource, null))).httpStatus(HTTP_BAD_REQUEST);
     }
 
     @Override
     public ApiResponse handleApplicationException(final ApiFailureResource resource, final RuntimeException cause) {
-        final int status;
         if (cause instanceof LoginUnauthorizedException) {
-            status = HTTP_UNAUTHORIZED; // unauthorized
+            return asJson(createFailureBean(Status.UNAUTHORIZED, "Unauthorized request.")).httpStatus(HTTP_UNAUTHORIZED);
         } else {
-            status = HTTP_BAD_REQUEST; // bad request
+            return asJson(createFailureBean(Status.BAD_REQUEST, createMessage(resource, cause))).httpStatus(HTTP_BAD_REQUEST);
         }
-        return asJson(createFailureBean(resource)).httpStatus(status);
     }
 
     // ===================================================================================
@@ -70,22 +71,22 @@ public class FessApiFailureHook implements ApiFailureHook { // #change_it for ha
     // ===================================================================================
     //                                                                        Assist Logic
     //                                                                        ============
-    protected JsonResponse<TooSimpleFailureBean> asJson(final TooSimpleFailureBean bean) {
+    protected JsonResponse<ApiResult> asJson(final ApiResult bean) {
         return new JsonResponse<>(bean);
     }
 
-    protected TooSimpleFailureBean createFailureBean(final ApiFailureResource resource) {
-        return new TooSimpleFailureBean(resource.getMessageList());
+    protected ApiResult createFailureBean(final Status status, final String message) {
+        return new ApiErrorResponse().message(message).status(status).result();
     }
 
-    public static class TooSimpleFailureBean {
-
-        public final String notice = "[Attension] tentative JSON so you should change it: " + FessApiFailureHook.class;
-
-        public final List<String> messageList;
-
-        public TooSimpleFailureBean(final List<String> messageList) {
-            this.messageList = messageList;
+    protected String createMessage(final ApiFailureResource resource, final RuntimeException cause) {
+        if (!resource.getMessageList().isEmpty()) {
+            return resource.getMessageList().stream().collect(Collectors.joining(" "));
         }
+        if (cause != null) {
+            return cause.getMessage();
+        }
+        return "Unknown error";
     }
+
 }

+ 1 - 0
src/main/resources/fess_config.properties

@@ -78,6 +78,7 @@ supported.uploaded.files=license.properties
 supported.languages=ar,bg,ca,da,de,el,en,es,eu,fa,fi,fr,ga,gl,hi,hu,hy,id,it,ja,lv,ko,nl,no,pt,ro,ru,sv,th,tr,zh_CN,zh_TW,zh
 api.access.token.length=60
 api.access.token.required=false
+api.admin.access.permissions=Radmin-api
 
 # ========================================================================================
 #                                                                                   Index

+ 1 - 0
src/main/resources/fess_message.properties

@@ -129,6 +129,7 @@ errors.failed_to_upgrade_from=Failed to upgrade from {0}: {1}
 errors.failed_to_read_request_file=Failed to read request file: {0}
 errors.invalid_header_for_request_file=Invalid header: {0}
 errors.could_not_delete_logged_in_user=Could not delete logged in user.
+errors.unauthorized_request=Unauthorized request.
 
 errors.invalid_query_unknown=The given query has unknown condition.
 errors.invalid_query_parse_error=The given query is invalid.

+ 1 - 0
src/main/resources/fess_message_en.properties

@@ -125,6 +125,7 @@ errors.failed_to_upgrade_from=Failed to upgrade from {0}.
 errors.failed_to_read_request_file=Failed to read request file: {0}
 errors.invalid_header_for_request_file=Invalid header: {0}
 errors.could_not_delete_logged_in_user=Could not delete logged in user.
+errors.unauthorized_request=Unauthorized request.
 
 errors.invalid_query_unknown=The given query has unknown condition.
 errors.invalid_query_parse_error=The given query is invalid.