dotCMS / core

Headless/Hybrid Content Management System for Enterprises
http://dotcms.com
Other
860 stars 467 forks source link

CLI: Improve error handling when running commands with restricted users #25582

Closed nollymar closed 1 year ago

nollymar commented 1 year ago

Parent Issue

24753

Problem Statement

As a restricted user, I'd like to be able to get a user friendly response each time I run a cli command. It is clear that some assets (or content-types, sites, etc) are restricted to limited users, but in this case, the user expects a good error handling response. It is:

This issue is related to: #25148 and the fix included on this card is applied to #25148 as well.

Steps to Reproduce

  1. Log into the cli with a limited user
  2. Try to pull/push any content type/host/language/file which the user has not permission to

Acceptance Criteria

The error handling on this scenario should be:

dotCMS Version

master

Proposed Objective

Core Features

Proposed Priority

Priority 3 - Average

External Links... Slack Conversations, Support Tickets, Figma Designs, etc.

No response

Assumptions & Initiation Needs

No response

Quality Assurance Notes & Workarounds

No response

Sub-Tasks & Estimates

No response

jgambarios commented 1 year ago

Areas to improve

  1. When using -e or --errors we should display the error message plus the exception, not just the exception. Example: login -u admin@dotcms.com -p badPassword vs login -u admin@dotcms.com -p badPassword --errors with the errors option we show the exception but we are losing details about what happen by removing the error detail.
login -u admin@dotcms.com -p badPassword

Logging in as [admin@dotcms.com].
[ERROR] ❗  Error in command [login] with message:
  HTTP 401 Unauthorized: Authentication failed or Permission is denied.
run with -e or --errors for full details on the exception.

VS

login -u admin@dotcms.com -p badPassword --errors

Logging in as [admin@dotcms.com].
io.quarkus.security.UnauthorizedException: Authentication failed.  Please try again.
    at com.dotcms.api.provider.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:55)
    at com.dotcms.api.provider.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:20)
    at org.jboss.resteasy.microprofile.client.ExceptionMapping$HandlerException.mapException(ExceptionMapping.java:41)
    at org.jboss.resteasy.microprofile.client.ProxyInvocationHandler.invoke(ProxyInvocationHandler.java:153)
    at com.sun.proxy.$Proxy87.getToken(Unknown Source)
    at com.dotcms.api.DefaultAuthenticationContextImpl.login(DefaultAuthenticationContextImpl.java:81)
    at com.dotcms.api.DefaultAuthenticationContextImpl_Subclass.login$$superforward1(Unknown Source)
    at com.dotcms.api.DefaultAuthenticationContextImpl_Subclass$$function$$8.apply(Unknown Source)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at com.dotcms.api.DefaultAuthenticationContextImpl_Subclass.login(Unknown Source)
    at com.dotcms.api.DefaultAuthenticationContextImpl_ClientProxy.login(Unknown Source)
    at com.dotcms.cli.command.LoginCommand.call(LoginCommand.java:48)
    at com.dotcms.cli.command.LoginCommand_Subclass.call$$superforward1(Unknown Source)
    at com.dotcms.cli.command.LoginCommand_Subclass$$function$$26.apply(Unknown Source)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:50)
    at io.quarkus.arc.impl.ActivateRequestContextInterceptor.aroundInvoke(ActivateRequestContextInterceptor.java:24)
    at io.quarkus.arc.impl.ActivateRequestContextInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at com.dotcms.cli.command.LoginCommand_Subclass.call(Unknown Source)
    at com.dotcms.cli.command.LoginCommand.call(LoginCommand.java:13)
    at picocli.CommandLine.executeUserObject(CommandLine.java:1953)
    at picocli.CommandLine.access$1300(CommandLine.java:145)
    at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2358)
    at picocli.CommandLine$RunLast.handle(CommandLine.java:2352)
    at picocli.CommandLine$RunLast.handle(CommandLine.java:2314)
    at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2179)
    at picocli.CommandLine$RunLast.execute(CommandLine.java:2316)
    at io.quarkus.picocli.runtime.PicocliRunner$EventExecutionStrategy.execute(PicocliRunner.java:26)
    at picocli.CommandLine.execute(CommandLine.java:2078)
    at io.quarkus.picocli.runtime.PicocliRunner.run(PicocliRunner.java:40)
    at io.quarkus.picocli.runtime.PicocliRunner_Subclass.run$$superforward1(Unknown Source)
    at io.quarkus.picocli.runtime.PicocliRunner_Subclass$$function$$1.apply(Unknown Source)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at io.quarkus.picocli.runtime.PicocliRunner_Subclass.run(Unknown Source)
    at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:124)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:67)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:41)
    at io.quarkus.runner.GeneratedMain.main(Unknown Source)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:103)
    at java.base/java.lang.Thread.run(Thread.java:834)

  1. Files Push: errors without --fail-fast VS with --fail-fast: We are losing the error detail.
# Errors without fail fast
files push /path/to/CLI/workspace

Found [1] errors during the push process:
[ERROR] ❗  Error pushing asset [/images/blogs/get.vtl] --- The file(s) does not match the filters specified in the folder properties
List of non valid fields
BADTYPE: hostFolder/Host Or Folder

# Errors with fail fast
files push /path/to/CLI/workspace --fail-fast

Error in command [push] with message:
  HTTP 400 Bad Request: The server encountered an error processing the request.
run with -e or --errors for full details on the exception.

  1. Running java -jar cli-1.0.0-SNAPSHOT-runner.jar site remove demo.dotcms.com results in:
java -jar cli-1.0.0-SNAPSHOT-runner.jar site remove demo.dotcms.com

Please confirm that you want to remove the site [demo.dotcms.com]  (y/N) ? y
[ERROR] ❗  Error in command [remove] with message:
  HTTP 400 Bad Request: The server encountered an error processing the request.
run with -e or --errors for full details on the exception.

But when running with the --errors option, the stack trace shows the error message is: the default site can't be deleted. We need a better description of the error using that message when we display the error description..

java -jar cli-1.0.0-SNAPSHOT-runner.jar site remove demo.dotcms.com

Please confirm that you want to remove the site [demo.dotcms.com]  (y/N) ? y
javax.ws.rs.WebApplicationException:  Status: (400) With error: {
  "message" : "the default site can't be deleted"
}
    at com.dotcms.api.provider.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:61)
    at com.dotcms.api.provider.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:20)
    at org.jboss.resteasy.microprofile.client.ExceptionMapping$HandlerException.mapException(ExceptionMapping.java:41)
    at org.jboss.resteasy.microprofile.client.ProxyInvocationHandler.invoke(ProxyInvocationHandler.java:153)
    at com.sun.proxy.$Proxy54.delete(Unknown Source)
    at com.dotcms.cli.command.site.SiteRemove.deleteSite(SiteRemove.java:64)
    at com.dotcms.cli.command.site.SiteRemove.delete(SiteRemove.java:55)
    at com.dotcms.cli.command.site.SiteRemove.call(SiteRemove.java:47)
    at com.dotcms.cli.command.site.SiteRemove_Subclass.call$$superforward1(Unknown Source)
    at com.dotcms.cli.command.site.SiteRemove_Subclass$$function$$6.apply(Unknown Source)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.impl.ActivateRequestContextInterceptor.aroundInvoke(ActivateRequestContextInterceptor.java:24)
    at io.quarkus.arc.impl.ActivateRequestContextInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at com.dotcms.cli.command.site.SiteRemove_Subclass.call(Unknown Source)
    at com.dotcms.cli.command.site.SiteRemove.call(SiteRemove.java:16)
    at picocli.CommandLine.executeUserObject(CommandLine.java:1953)
    at picocli.CommandLine.access$1300(CommandLine.java:145)
    at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2358)
    at picocli.CommandLine$RunLast.handle(CommandLine.java:2352)
    at picocli.CommandLine$RunLast.handle(CommandLine.java:2314)
    at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2179)
    at picocli.CommandLine$RunLast.execute(CommandLine.java:2316)
    at io.quarkus.picocli.runtime.PicocliRunner$EventExecutionStrategy.execute(PicocliRunner.java:26)
    at picocli.CommandLine.execute(CommandLine.java:2078)
    at io.quarkus.picocli.runtime.PicocliRunner.run(PicocliRunner.java:40)
    at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:124)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:67)
    at io.quarkus.runtime.Quarkus.run(Quarkus.java:41)
    at io.quarkus.runner.GeneratedMain.main(Unknown Source)
fabrizzio-dotCMS commented 1 year ago

Note to QA: the CLI is a direct consumer of our RestAPI Unfortunately the exception codes and error messages returned not always reflect the situation e.g. When a user has limited access to a site a 404 is returned some other times instead of the Error text itself we get an i18n code. Therefore I decided to replace the incoming error message with a generic one to somehow make it more consistent. The detailed underlying error including a stack trace can be seen upon setting -e or --error This behavior can be overridden with the use of an environment var like

export DOTCMS_STATUS_CONF_OVERRIDE=false
alias dot='java -jar /Users/.../core/tools/dotcms-cli/cli/target/cli-1.0.0-SNAPSHOT-runner.jar'

Then let's give it a try and compare and decide if we prefer the original message rather than the generic one.