Sunday, 21 July 2019

Geoserver REST: Change/create style via PUT/POST only possible when user is assigned admin role


I am fairly new to Geoserver REST API and security settings, so please excuse me if I am missing the absolute obvious here. I am running Geoserver 2.11.2 running on Tomcat 7 on a Win7 64bit system. I have written a web client to manipulate styles via REST API to enable users without admin rights to edit/upload styles.


Basically I'm using jquery $.ajax-Functions to HTTP GET a template style from Geoserver, edit it locally and upload it via HTTP PUT or create new one via HTTP POST. Base URL is



http://ip:8080/geoserver/rest/styles/style_name

Using the default settings in file rest.properties ($geoserver_home/data/security) everything works as intended when using admin credentials after authentication is prompted (Status Code 200 OK). Style is being fetched/edited/created successfully. Default ANT patterns in rest.properties:


/**;GET=ADMIN
/**;POST,DELETE,PUT=ADMIN

I then introduced a new user called 'chief' and assigned two new roles called ROLE_REST_READ and ROLE_REST_WRITE to it. Following the Geoserver docs for changing REST security I changed rest.properties to:


/**;GET=ROLE_REST_READ
/**;POST,DELETE,PUT=ROLE_REST_WRITE


Now when entering the new 'chief' credentials when prompted, the same HTTP PUT results in


Status Code: 405 Method not allowed
[...]
Response Headers Allow: DELETE,GET

and the same HTTP POST results in


Status Code: 405 Method not allowed
[...]
Response Headers Allow: GET


However HTTP GET still works as before. I then tried to changed the ANT pattern to


/rest/**;GET=ROLE_REST_READ
/rest/**;POST,PUT,DELETE=ROLE_REST_WRITE

with no effect.


Geoserver Logs (debug mode) for HTTP GET (still successful)


2017-09-18 11:38:22,796 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/web/**'
2017-09-18 11:38:22,796 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/gwc/rest/web/**'
2017-09-18 11:38:22,796 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/'
2017-09-18 11:38:22,796 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/j_spring_security_check'

2017-09-18 11:38:22,796 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/j_spring_security_check/'
2017-09-18 11:38:22,796 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/j_spring_security_logout'
2017-09-18 11:38:22,796 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/j_spring_security_logout/'
2017-09-18 11:38:22,796 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/rest/**'
2017-09-18 11:38:22,800 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Matched Path: /rest/styles/styleTemplate.sld, QueryString: null with /rest/**
2017-09-18 11:38:22,801 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Converted URL to lowercase, from: '/rest/styles/styleTemplate.sld'; to: '/rest/styles/styleTemplate.sld' and httpMethod= GET
2017-09-18 11:38:22,801 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - ~~~~~~~~~~ antPath= /** methodList= [GET]
2017-09-18 11:38:22,801 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Candidate is: '/rest/styles/styleTemplate.sld'; antPath is /**; matchedPath=true; matchedMethods=true
2017-09-18 11:38:22,801 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - returning ROLE_REST_READ
2017-09-18 11:38:22,802 DEBUG [org.geoserver.security.filter.GeoServerSecurityContextPersistenceFilter$1] - SecurityContextHolder now cleared, as request processing completed

2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/web/**'
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/gwc/rest/web/**'
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/'
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/j_spring_security_check'
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/j_spring_security_check/'
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/j_spring_security_logout'
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/j_spring_security_logout/'
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/styleTemplate.sld, QueryString: null'; against '/rest/**'
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Matched Path: /rest/styles/styleTemplate.sld, QueryString: null with /rest/**
2017-09-18 11:38:30,457 DEBUG [org.geoserver.security] - AuthenticationCache has no entry for basic, chief:6c4820b1f41dbfffe754a886365ef4b2

2017-09-18 11:38:30,552 DEBUG [org.geoserver.security.rememberme.GeoServerTokenBasedRememberMeServices] - Did not send remember-me cookie (principal did not set parameter '_spring_security_remember_me')
2017-09-18 11:38:30,552 DEBUG [org.geoserver.security.rememberme.GeoServerTokenBasedRememberMeServices] - Remember-me login not requested.
2017-09-18 11:38:30,552 DEBUG [org.geoserver.security] - AuthenticationCache adding new entry for basic, chief:6c4820b1f41dbfffe754a886365ef4b2
2017-09-18 11:38:30,552 DEBUG [org.geoserver.security] - Cache entries #: 0
2017-09-18 11:38:30,552 DEBUG [org.geoserver.security] - AuthenticationCache added new entry for basic, chief:6c4820b1f41dbfffe754a886365ef4b2
2017-09-18 11:38:30,552 DEBUG [org.geoserver.security] - Cache entries #: 1
2017-09-18 11:38:30,553 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Converted URL to lowercase, from: '/rest/styles/styleTemplate.sld'; to: '/rest/styles/styleTemplate.sld' and httpMethod= GET
2017-09-18 11:38:30,553 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - ~~~~~~~~~~ antPath= /** methodList= [GET]
2017-09-18 11:38:30,553 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Candidate is: '/rest/styles/styleTemplate.sld'; antPath is /**; matchedPath=true; matchedMethods=true
2017-09-18 11:38:30,553 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - returning ROLE_REST_READ

2017-09-18 11:38:30,553 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,553 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles/styleTemplate.sld]
2017-09-18 11:38:30,553 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,553 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles/styleTemplate.sld]
2017-09-18 11:38:30,553 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,553 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles/styleTemplate.sld]
2017-09-18 11:38:30,554 DEBUG [org.geoserver] - Thread 56 locking in mode READ
2017-09-18 11:38:30,554 DEBUG [org.geoserver] - Thread 56 got the lock in mode READ
2017-09-18 11:38:30,592 DEBUG [org.geoserver.catalog.rest] - GET style styleTemplate
2017-09-18 11:38:30,592 DEBUG [org.geoserver] - Thread 56 locking in mode READ

2017-09-18 11:38:30,592 DEBUG [org.geoserver] - Thread 56 releasing the lock in mode READ
2017-09-18 11:38:30,592 DEBUG [org.geoserver.filters] - Compressing output for mimetype: application/vnd.ogc.sld+xml
2017-09-18 11:38:30,593 DEBUG [org.geoserver.security.filter.GeoServerSecurityContextPersistenceFilter$1] - SecurityContextHolder now cleared, as request processing completed

Geoserver Logs (debug mode) for HTTP PUT/POST (unsuccessful)


2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/stylename, QueryString: null'; against '/web/**'
2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/stylename, QueryString: null'; against '/gwc/rest/web/**'
2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/stylename, QueryString: null'; against '/'
2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/stylename, QueryString: null'; against '/j_spring_security_check'
2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/stylename, QueryString: null'; against '/j_spring_security_check/'

2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/stylename, QueryString: null'; against '/j_spring_security_logout'
2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/stylename, QueryString: null'; against '/j_spring_security_logout/'
2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles/stylename, QueryString: null'; against '/rest/**'
2017-09-18 11:38:30,608 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Matched Path: /rest/styles/stylename, QueryString: null with /rest/**
2017-09-18 11:38:30,608 DEBUG [org.geoserver.security] - AuthenticationCache found an entry for basic, chief:6c4820b1f41dbfffe754a886365ef4b2
2017-09-18 11:38:30,609 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Converted URL to lowercase, from: '/rest/styles/stylename'; to: '/rest/styles/stylename' and httpMethod= PUT
2017-09-18 11:38:30,609 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - ~~~~~~~~~~ antPath= /** methodList= [GET]
2017-09-18 11:38:30,609 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Candidate is: '/rest/styles/stylename'; antPath is /**; matchedPath=true; matchedMethods=false
2017-09-18 11:38:30,609 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - ~~~~~~~~~~ antPath= /** methodList= [POST, DELETE, PUT]
2017-09-18 11:38:30,609 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Candidate is: '/rest/styles/stylename'; antPath is /**; matchedPath=true; matchedMethods=true

2017-09-18 11:38:30,609 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - returning ROLE_REST_WRITE
2017-09-18 11:38:30,609 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,609 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles/stylename]
2017-09-18 11:38:30,609 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,609 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles/stylename]
2017-09-18 11:38:30,609 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,609 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles/stylename]
2017-09-18 11:38:30,610 DEBUG [org.geoserver] - Thread 56 locking in mode WRITE
2017-09-18 11:38:30,610 DEBUG [org.geoserver] - Thread 56 got the lock in mode WRITE
2017-09-18 11:38:30,611 DEBUG [org.geoserver] - Thread 56 locking in mode WRITE

2017-09-18 11:38:30,611 DEBUG [org.geoserver] - Thread 56 releasing the lock in mode WRITE
2017-09-18 11:38:30,611 DEBUG [org.geoserver.security.filter.GeoServerSecurityContextPersistenceFilter$1] - SecurityContextHolder now cleared, as request processing completed
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/web/**'
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/gwc/rest/web/**'
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/'
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/j_spring_security_check'
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/j_spring_security_check/'
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/j_spring_security_logout'
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/j_spring_security_logout/'
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/rest/**'

2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Matched Path: /rest/styles, QueryString: null with /rest/**
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Converted URL to lowercase, from: '/rest/styles'; to: '/rest/styles' and httpMethod= POST
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - ~~~~~~~~~~ antPath= /** methodList= [GET]
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Candidate is: '/rest/styles'; antPath is /**; matchedPath=true; matchedMethods=false
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - ~~~~~~~~~~ antPath= /** methodList= [POST, DELETE, PUT]
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Candidate is: '/rest/styles'; antPath is /**; matchedPath=true; matchedMethods=true
2017-09-18 11:38:30,623 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - returning ROLE_REST_WRITE
2017-09-18 11:38:30,624 DEBUG [org.geoserver.security.filter.GeoServerSecurityContextPersistenceFilter$1] - SecurityContextHolder now cleared, as request processing completed
2017-09-18 11:38:30,625 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/web/**'
2017-09-18 11:38:30,625 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/gwc/rest/web/**'

2017-09-18 11:38:30,625 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/'
2017-09-18 11:38:30,625 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/j_spring_security_check'
2017-09-18 11:38:30,625 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/j_spring_security_check/'
2017-09-18 11:38:30,625 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/j_spring_security_logout'
2017-09-18 11:38:30,625 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/j_spring_security_logout/'
2017-09-18 11:38:30,626 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Checking match of request : 'Path: /rest/styles, QueryString: null'; against '/rest/**'
2017-09-18 11:38:30,626 DEBUG [org.geoserver.security.IncludeQueryStringAntPathRequestMatcher] - Matched Path: /rest/styles, QueryString: null with /rest/**
2017-09-18 11:38:30,627 DEBUG [org.geoserver.security] - AuthenticationCache found an entry for basic, chief:6c4820b1f41dbfffe754a886365ef4b2
2017-09-18 11:38:30,627 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Converted URL to lowercase, from: '/rest/styles'; to: '/rest/styles' and httpMethod= POST
2017-09-18 11:38:30,627 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - ~~~~~~~~~~ antPath= /** methodList= [GET]

2017-09-18 11:38:30,627 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Candidate is: '/rest/styles'; antPath is /**; matchedPath=true; matchedMethods=false
2017-09-18 11:38:30,627 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - ~~~~~~~~~~ antPath= /** methodList= [POST, DELETE, PUT]
2017-09-18 11:38:30,627 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - Candidate is: '/rest/styles'; antPath is /**; matchedPath=true; matchedMethods=true
2017-09-18 11:38:30,627 DEBUG [org.geoserver.security.RESTfulPathBasedFilterInvocationDefinitionMap] - returning ROLE_REST_WRITE
2017-09-18 11:38:30,627 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,627 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles]
2017-09-18 11:38:30,627 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,627 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles]
2017-09-18 11:38:30,627 DEBUG [org.geoserver.ows] - Could not a layer group named rest
2017-09-18 11:38:30,627 TRACE [org.geoserver.ows.OWSHandlerMapping] - No handler mapping found for [/rest/styles]

2017-09-18 11:38:30,628 DEBUG [org.geoserver] - Thread 56 locking in mode WRITE
2017-09-18 11:38:30,628 DEBUG [org.geoserver] - Thread 56 got the lock in mode WRITE
2017-09-18 11:38:30,629 DEBUG [org.geoserver] - Thread 56 locking in mode WRITE
2017-09-18 11:38:30,629 DEBUG [org.geoserver] - Thread 56 releasing the lock in mode WRITE
2017-09-18 11:38:30,629 DEBUG [org.geoserver.security.filter.GeoServerSecurityContextPersistenceFilter$1] - SecurityContextHolder now cleared, as request processing completed

From what I can see the Logs of a successful PUT/POST attempt using admin credentials do not differ in any way regarding the REST function outputs. What is it I am missing here? Does the user really needs to be assigned an admin role? Is there maybe something wrong with my authentication chain?



Answer



Well, I think I can answer my own question here, kind of. After scratching my head for a while I realized that the '405 Not allowed' had nothing to do with the settings under rest.properties, but with the 'rest' chain under Authentication->filter chain.


After fiddling around a bit I switched chain security off under rest chain settings. This solution is ok for my case (I just don't want the user 'chief' to have admin rights for web config), but still isn't optimal because of REST interface being completely open now. I still wasn't able to grant user 'chief' the privileges specified under rest.properties with rest filter chain security settings in place, but this topic I will now move to a new and more specific question



No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...