Metabase API PUT Permissions Graph

We are attempting to update the permissons graph for tables and groups via the API /api/permissions/graph. We have the API call working, retrieving the original permissions graph via the GET. This is then updated and sent back using the PUT method. We are getting a 500 each time. I can send the Java output of the two graphs (Map of Maps; pre-update and updated), if that is helpful, but they appear identical except for the new group/table mapping. Is there anything that jumps out in the below Metabase logs error output? Thanks in advance.

[a929afc9-7bd3-49e7-ae47-0ab6e8fc55b4] 2023-04-06T21:08:54-04:00 DEBUG metabase.server.middleware.log GET /api/permissions/graph 200 4.8 ms (3 DB calls) App DB connections: 0/15 Jetty threads: 5/50 (2 idle, 0 queued) (53 total active threads) Queries in flight: 0 (0 queued)
[a929afc9-7bd3-49e7-ae47-0ab6e8fc55b4] 2023-04-06T21:08:54-04:00 ERROR metabase.server.middleware.log PUT /api/permissions/graph 500 685.1 µs (0 DB calls)
{:via
[{:type clojure.lang.ExceptionInfo,
:message
"Input to update-graph! does not match schema: \n\n\t [(named (not (map? a-clojure.lang.Keyword)) new-graph)] \n\n",
:data
{:type :schema.core/error,
:schema
[{:schema
{:revision Int,
:groups
{(constrained Int "Integer greater than zero")
{(constrained Int "Integer greater than zero")
(constrained
(named
{(optional-key :native) (named (enum :write :none) "Valid native perms option for a database"),
(optional-key :schemas)
(cond-pre
(enum :all :none)
{Str
(named
(cond-pre
(enum :all :none)
{(constrained Int "Integer greater than zero")
(named
(cond-pre
(enum :all :none)
(constrained
{(optional-key :read) (enum :all :none), (optional-key :query) (enum :all :segmented :none)}
not-empty))
"Valid perms graph for a Table")})
"Valid perms graph for a schema")})}
"Valid perms graph for a Database")
"DB permissions with a valid combination of values for :native and :schemas")}}},
:optional? false,
:name new-graph}],
:value [:clojure.spec.alpha/invalid],
:error [(named (not (map? a-clojure.lang.Keyword)) new-graph)]},
:at [metabase.models.permissions$fn__32778$update_graph_BANG___32787 invoke "permissions.clj" 701]}],
:trace
[[metabase.models.permissions$fn__32778$update_graph_BANG___32787 invoke "permissions.clj" 701]
[metabase.api.permissions$fn__72439 invokeStatic "permissions.clj" 39]
[metabase.api.permissions$fn__72439 invoke "permissions.clj" 27]
[compojure.core$wrap_response$fn__1996 invoke "core.clj" 160]
[compojure.core$wrap_route_middleware$fn__1980 invoke "core.clj" 132]
[compojure.core$wrap_route_info$fn__1985 invoke "core.clj" 139]
[compojure.core$wrap_route_matches$fn__1989 invoke "core.clj" 151]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$wrap_route_matches$fn__1989 invoke "core.clj" 153]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$wrap_route_matches$fn__1989 invoke "core.clj" 153]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008 invoke "core.clj" 200]
[metabase.server.middleware.auth$enforce_authentication$fn__75605 invoke "auth.clj" 14]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008 invoke "core.clj" 200]
[compojure.core$make_context$handler__2036 invoke "core.clj" 287]
[compojure.core$make_context$fn__2038 invoke "core.clj" 296]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$make_context$fn__2038 invoke "core.clj" 297]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[metabase.api.routes$fn__75727$fn__75729 invoke "routes.clj" 70]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008 invoke "core.clj" 200]
[clojure.lang.AFn applyToHelper "AFn.java" 160]
[clojure.lang.AFn applyTo "AFn.java" 144]
[clojure.core$apply invokeStatic "core.clj" 665]
[clojure.core$apply invoke "core.clj" 660]
[metabase.server.routes$fn__77998$fn__77999 doInvoke "routes.clj" 57]
[clojure.lang.RestFn invoke "RestFn.java" 436]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008 invoke "core.clj" 200]
[compojure.core$make_context$handler__2036 invoke "core.clj" 287]
[compojure.core$make_context$fn__2038 invoke "core.clj" 296]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$wrap_route_matches$fn__1989 invoke "core.clj" 153]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$wrap_route_matches$fn__1989 invoke "core.clj" 153]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[compojure.core$wrap_route_matches$fn__1989 invoke "core.clj" 153]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008$f__2009$respond_SINGLEQUOTE___2010 invoke "core.clj" 197]
[metabase.server.routes$fn__77986$fn__77988 invoke "routes.clj" 41]
[compojure.core$routes$fn__2008$f__2009 invoke "core.clj" 198]
[compojure.core$routes$fn__2008 invoke "core.clj" 200]
[metabase.server.middleware.exceptions$catch_uncaught_exceptions$fn__75718 invoke "exceptions.clj" 98]
[metabase.server.middleware.exceptions$catch_api_exceptions$fn__75715 invoke "exceptions.clj" 86]
[metabase.server.middleware.log$log_api_call$fn__77914$fn__77915 invoke "log.clj" 195]
[toucan.db$_do_with_call_counting invokeStatic "db.clj" 216]
[toucan.db$_do_with_call_counting invoke "db.clj" 209]
[metabase.server.middleware.log$log_api_call$fn__77914 invoke "log.clj" 189]
[metabase.server.middleware.browser_cookie$ensure_browser_id_cookie$fn__77550 invoke "browser_cookie.clj" 32]
[metabase.server.middleware.security$add_security_headers$fn__75680 invoke "security.clj" 142]
[metabase.server.middleware.json$wrap_json_body$fn__77695 invoke "json.clj" 62]
[metabase.server.middleware.json$wrap_streamed_json_response$fn__77713 invoke "json.clj" 98]
[ring.middleware.keyword_params$wrap_keyword_params$fn__78255 invoke "keyword_params.clj" 55]
[ring.middleware.params$wrap_params$fn__78271 invoke "params.clj" 69]
[metabase.server.middleware.misc$maybe_set_site_url$fn__35522 invoke "misc.clj" 58]
[metabase.server.middleware.session$bind_current_user$fn__42494$fn__42495 invoke "session.clj" 248]
[metabase.server.middleware.session$do_with_current_user invokeStatic "session.clj" 229]
[metabase.server.middleware.session$do_with_current_user invoke "session.clj" 221]
[metabase.server.middleware.session$bind_current_user$fn__42494 invoke "session.clj" 247]
[metabase.server.middleware.session$wrap_current_user_info$fn__42481 invoke "session.clj" 207]
[metabase.server.middleware.session$wrap_session_id$fn__42467 invoke "session.clj" 153]
[metabase.server.middleware.auth$wrap_api_key$fn__75613 invoke "auth.clj" 27]
[ring.middleware.cookies$wrap_cookies$fn__78175 invoke "cookies.clj" 216]
[metabase.server.middleware.misc$add_content_type$fn__35505 invoke "misc.clj" 27]
[metabase.server.middleware.misc$disable_streaming_buffering$fn__35530 invoke "misc.clj" 75]
[ring.middleware.gzip$wrap_gzip$fn__78217 invoke "gzip.clj" 86]
[metabase.server.middleware.misc$bind_request$fn__35533 invoke "misc.clj" 92]
[metabase.server.middleware.ssl$redirect_to_https_middleware$fn__77931 invoke "ssl.clj" 38]
[metabase.server$async_proxy_handler$fn__77494 invoke "server.clj" 71]
[metabase.server.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a handle nil -1]
[org.eclipse.jetty.server.handler.HandlerWrapper handle "HandlerWrapper.java" 127]
[org.eclipse.jetty.server.Server handle "Server.java" 516]
[org.eclipse.jetty.server.HttpChannel lambda$handle$1 "HttpChannel.java" 383]
[org.eclipse.jetty.server.HttpChannel dispatch "HttpChannel.java" 556]
[org.eclipse.jetty.server.HttpChannel handle "HttpChannel.java" 375]
[org.eclipse.jetty.server.HttpConnection onFillable "HttpConnection.java" 273]
[org.eclipse.jetty.io.AbstractConnection$ReadCallback succeeded "AbstractConnection.java" 311]
[org.eclipse.jetty.io.FillInterest fillable "FillInterest.java" 105]
[org.eclipse.jetty.io.ChannelEndPoint$1 run "ChannelEndPoint.java" 104]
[org.eclipse.jetty.util.thread.strategy.EatWhatYouKill runTask "EatWhatYouKill.java" 336]
[org.eclipse.jetty.util.thread.strategy.EatWhatYouKill doProduce "EatWhatYouKill.java" 313]
[org.eclipse.jetty.util.thread.strategy.EatWhatYouKill tryProduce "EatWhatYouKill.java" 171]
[org.eclipse.jetty.util.thread.strategy.EatWhatYouKill run "EatWhatYouKill.java" 129]
[org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread run "ReservedThreadExecutor.java" 375]
[org.eclipse.jetty.util.thread.QueuedThreadPool runJob "QueuedThreadPool.java" 773]
[org.eclipse.jetty.util.thread.QueuedThreadPool$Runner run "QueuedThreadPool.java" 905]
[java.lang.Thread run nil -1]],
:cause
"Input to update-graph! does not match schema: \n\n\t [(named (not (map? a-clojure.lang.Keyword)) new-graph)] \n\n",
:data
{:type :schema.core/error,
:schema
[{:schema
{:revision Int,
:groups
{(constrained Int "Integer greater than zero")
{(constrained Int "Integer greater than zero")
(constrained
(named
{(optional-key :native) (named (enum :write :none) "Valid native perms option for a database"),
(optional-key :schemas)
(cond-pre
(enum :all :none)
{Str
(named
(cond-pre
(enum :all :none)
{(constrained Int "Integer greater than zero")
(named
(cond-pre
(enum :all :none)
(constrained
{(optional-key :read) (enum :all :none), (optional-key :query) (enum :all :segmented :none)}
not-empty))
"Valid perms graph for a Table")})
"Valid perms graph for a schema")})}
"Valid perms graph for a Database")
"DB permissions with a valid combination of values for :native and :schemas")}}},
:optional? false,
:name new-graph}],
:value [:clojure.spec.alpha/invalid],
:error [(named (not (map? a-clojure.lang.Keyword)) new-graph)]},
:message
"Input to update-graph! does not match schema: \n\n\t [(named (not (map? a-clojure.lang.Keyword)) new-graph)] \n\n",
:value [:clojure.spec.alpha/invalid],
:error [(named (not (map? a-clojure.lang.Keyword)) new-graph)]}

Please post diagnostic info

Here is the log output from Java program. Please let me know if something else would be more helpful. Thanks again.

Original permissions graph:
{revision=7, groups={2={1={native=write, schemas=all}, 13={native=write, schemas=all}, 12={native=write, schemas=all}, 11={native=write, schemas=all}, 14={native=write, schemas=all}}, 1={1={native=write, schemas=all}, 14={native=write, schemas=all}}, 11={11={native=write, schemas=all}}, 12={12={native=write, schemas=all}}, 13={13={native=write, schemas=all}}}}

Editing permissions graph:
{revision=7, groups={2={1={native=write, schemas=all}, 13={native=write, schemas=all}, 12={native=write, schemas=all}, 11={native=write, schemas=all}, 14={native=write, schemas=all}}, 1={1={native=write, schemas=all}, 14={native=write, schemas=all}}, 11={11={native=write, schemas=all}}, 12={12={native=write, schemas=all}}, 13={13={native=write, schemas=all}}, 14={14={native=write, schemas=all}}}}

Diagnostic info is the json you get when you go to settings->admin->troubleshooting info

{
"browser-info": {
"language": "en-US",
"platform": "Win32",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36",
"vendor": "Google Inc."
},
"system-info": {
"file.encoding": "UTF-8",
"java.runtime.name": "OpenJDK Runtime Environment",
"java.runtime.version": "11.0.11+9",
"java.vendor": "AdoptOpenJDK",
"java.vendor.url": "https://adoptopenjdk.net/",
"java.version": "11.0.11",
"java.vm.name": "OpenJDK 64-Bit Server VM",
"java.vm.version": "11.0.11+9",
"os.name": "Linux",
"os.version": "4.19.128-microsoft-standard",
"user.language": "en",
"user.timezone": "GMT"
},
"metabase-info": {
"databases": [
"mongo",
"h2"
],
"hosting-env": "unknown",
"application-database": "postgres",
"application-database-details": {
"database": {
"name": "PostgreSQL",
"version": "14.4 (Debian 14.4-1.pgdg110+1)"
},
"jdbc-driver": {
"name": "PostgreSQL JDBC Driver",
"version": "42.2.18"
}
},
"run-mode": "prod",
"version": {
"date": "2021-04-27",
"tag": "v0.39.1",
"branch": "release-x.39.x",
"hash": "6beba48"
},
"settings": {
"report-timezone": null
}
}
}

You’re using a very old version of Metabase, we released a fix of the issue you’re seeing in the most recent versions

Thanks again for your assistance. I upgraded and am running regression tests. The update permissions graph PUT call is still now working, message saying invalid schema. However, while running regression tests we now get the following error:

[ace5050b-5a88-4f18-bbbf-482b20e0c58d] 2023-04-07T17:42:53-04:00 DEBUG metabase.server.middleware.log POST /api/permissions/membership 402 3.2 ms (0 DB calls)
"The group manager permissions functionality is only enabled if you have a premium token with the advanced-permissions feature."

Do we need a different license to use all API methods now?

no, api's are included in the oss product. You seem to be calling the api with a wrongly formed json, as we introduced group managers in the recent versions and that might be the error you're seeing

The is_group_manager parameter is set. I can look at it and get back to you. Thanks again.

For the update permissions graph PUT, do we need to manually update the revision number before sending the request?

The update permissions graph PUT is now working. Issue was incorrectly wrapping the request body payload. See here. Per linked topic, revision number will be programmatically incremented only following CONFLICT (409) response. This topic is answered for now. Thanks.