Example #1
0
def restricted_resource_show(context, data_dict=None):
    # Ensure user who can edit the package can see the resource
    resource = data_dict.get('resource', context.get('resource', {}))
    if not resource:
        resource = logic_auth.get_resource_object(context, data_dict)
    if type(resource) is not dict:
        resource = resource.as_dict()
    if authz.is_authorized('package_update', context, {
            'id': resource.get('package_id')
    }).get('success'):
        return ({'success': True})

    # custom restricted check
    auth_user_obj = context.get('auth_user_obj', None)
    user_name = ""
    if auth_user_obj:
        user_name = auth_user_obj.as_dict().get('name', '')
    else:
        if authz.get_user_id_for_username(context.get('user'),
                                          allow_none=True):
            user_name = context.get('user', '')

    package = data_dict.get('package', {})
    if not package:
        model = context['model']
        package = model.Package.get(resource.get('package_id'))
        package = package.as_dict()

    return logic.restricted_check_user_resource_access(user_name, resource,
                                                       package)
Example #2
0
def restricted_check_access(context, data_dict):

    package_id = data_dict.get('package_id', False)
    resource_id = data_dict.get('resource_id', False)

    user_name = logic.restricted_get_username_from_context(context)

    if not package_id:
        raise ValidationError('Missing package_id')
    if not resource_id:
        raise ValidationError('Missing resource_id')

    log.debug("action.restricted_check_access: user_name = " + str(user_name))

    log.debug("checking package " + str(package_id))
    package_dict = get_action('package_show')(dict(context,
                                                   return_type='dict'), {
                                                       'id': package_id
                                                   })
    log.debug("checking resource")
    resource_dict = get_action('resource_show')(dict(context,
                                                     return_type='dict'), {
                                                         'id': resource_id
                                                     })

    return logic.restricted_check_user_resource_access(user_name,
                                                       resource_dict,
                                                       package_dict)
Example #3
0
def restricted_resource_show(context, data_dict=None):

    # Ensure user who can edit the package can see the resource
    resource = data_dict.get('resource', context.get('resource', {}))
    if not resource:
        resource = logic_auth.get_resource_object(context, data_dict)
    if type(resource) is not dict:
        resource = resource.as_dict()

    if authz.is_authorized(
            'package_update', context,
            {'id': resource.get('package_id')}).get('success'):
        return ({'success': True})

    user_name = logic.restricted_get_username_from_context(context)

    package = data_dict.get('package', {})
    if not package:
        logger.warning('restricted_resource_show was called without a Package in data_dict. Extra API call is required')
        model = context['model']
        package = model.Package.get(resource.get('package_id'))
        package = package.as_dict()

    return (logic.restricted_check_user_resource_access(
        user_name, resource, package))
    def map_preview_auth(self, resource_id, user_gs_layer):
        """
        Authorization check for the given resource ID and Geoserver layer name
        :param resource_id: resource_id embedded in map preview URL
        :param user_gs_layer: Geoserver layer name requested by user in the URL
        :return:
        """
        """
        Test cases:
        User mpeterman-msea-read.
        Testing with dataset: https://www.gis-hub.ca/dataset/test05.  This dataset has a mixture of 
        resources with restrictions. Test the following URLs:
        1. This resource is accessible to all users in the MSEA org: 
        https://www.gis-hub.ca/dataset/test05/map_preview/7fad5dba-b70e-4a01-b692-a59a9b1b100c
        Result: 200 Access OK
        2. Accessible only to user dfo or admins:
        https://www.gis-hub.ca/dataset/test05/map_preview/b1a24c96-eaa6-4433-a706-99854894be13

        Experimenting with NGINX URL rewrite and variable capture
        https://www.gis-hub.ca/nginx_auth/geoserver/rest/workspaces/hubdata/datastores/maps.gis-hub.ca/featuretypes/ia_geography_pncima_ia_oceanography_pncima.json
        https://www.gis-hub.ca/geoserver/rest/workspaces/hubdata/datastores/maps.gis-hub.ca/featuretypes/ia_geography_pncima_ia_oceanography_pncima.json

        Capture block in nginx has changed, the above URLs should now be:
        https://www.gis-hub.ca/map_preview/test05/7fad5dba-b70e-4a01-b692-a59a9b1b100c
        https://www.gis-hub.ca/map_preview/test05/b1a24c96-eaa6-4433-a706-99854894be13

        This NGINX location block matches after /geoserver, but it removes all the URL params after ?
        So, /hubdata/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap... etc 
        gets reduced to:
        /hubdata/wms
        # location ~ ^/map_preview/(?<package_id>.+)/(?<resource_id>.+)/geoserver/(?<geo_params>.+)$ {

        Need to add $query_string to the end of the proxy URI that gets passed on to geoserver on maps.gis-hub.ca
        set $geoserver_uri "https://maps.gis-hub.ca/geoserver/$geo_params?$query_string";
        Have to open this in a Chrome Private window, because there was some cached HTTP basic auth that it tried to use. 
        This works! We get the dataset and resource id, check it against CKAN auth, if it passes, we construct the correct geoserver 
        URL and pass it to the proxy.

        With a geoserver request appended, test proxy.
        https://www.gis-hub.ca/map_preview/test05/7fad5dba-b70e-4a01-b692-a59a9b1b100c/geoserver/hubdata/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&LAYERS=hubdata%3Aenv_layers_nsbssb_bpi_medium&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&STYLES=&WIDTH=873&HEIGHT=801&BBOX=-15384022.060787713%2C5832451.006272089%2C-13248677.23861303%2C7791684.915277727

        Test with a restricted resource, should fail:
        https://www.gis-hub.ca/map_preview/test05/b1a24c96-eaa6-4433-a706-99854894be13/geoserver/hubdata/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&LAYERS=hubdata%3Aenv_layers_nsbssb_bpi_medium&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A3857&STYLES=&WIDTH=873&HEIGHT=801&BBOX=-15384022.060787713%2C5832451.006272089%2C-13248677.23861303%2C7791684.915277727
        Yes, this request never makes it to the proxy, the auth check in nginx fails with error 500.  

        For now, just during development, we can keep the previous /geoserver proxy open, so that map previews will continue to work. 

        TODO: ensure that the resource being requested matches the WMS or vector layer in Geoserver. This can be deferred for now. 
        We dont care about requests like /vector/:dataset/:layer_name. The only thing we care about is securing actual map tile requests. 

        Probably need to also add the resource ID to the map preview URL, something like: 
        A: /vector/:dataset/:layer_name/:resource_id. 
        Then, inside the vector tiles template, use a URL like this:
        B: {{ host }}/map_preview/{{dataset}}/{{resource_id}}/geoserver/gwc/service/tms/1.0.0/hubdata:{{ layer_name }}...
        This will be sent to nginx auth, then proxy directly to: 
        C: https://maps.gis-hub.ca/geoserver/$geo_params?$query_string  << this part does not touch the Node.js mapserver middleware at all.
        And this all happens *inside NGINX*, the only URL the user's browser sees is A and B

        ** Update May 20. For some reason, the map_preview endpoint has stopped working. e.g.
        https://www.gis-hub.ca/dataset/test05/map_preview/7fad5dba-b70e-4a01-b692-a59a9b1b100c throws not found, this controller function is 
        not called at all. > Because I renamed the function to map_preview_auth! 

        Test this with:
        https://maps.gis-hub.ca/vector/ia_geography_pncima/ia_oceanography_pncima/f63a3614-84ae-4168-b340-6919eac458c6

        DOES NOT WORK because: ia_geography_pncima > in CKAN this should be ia-geography-pncima with HYPHENS
        However in mapserver backend, it is using ia_geography_pncima + resource title with UNDERSCORES to check map extent and so on. 
        Need to replace the dataset part of the map preview URL with ia-geography-pncima, then in the Node backend, convert this to 
        underscores before generating postgis layer name. Postgis is not happy with table names containing hyphens. What a pain. 

        Change to: 
        https://maps.gis-hub.ca/vector/ia-geography-pncima/ia_oceanography_pncima/f63a3614-84ae-4168-b340-6919eac458c6
        Now the last issue we have is that when the request gets proxied from maps.gis-hub.ca back to gis-hub, it no longer has the CKAN 
        context object with the user. Is is possible to pass the session with the proxied headers? If not, we might have to redo the 
        map preview code inside a CKAN extension (where we know for sure that we have the context). This would simplify things somewhat, 
        no need for all the proxying between the two servers. 

        https://maps.gis-hub.ca/vector/ia-geography-pncima/ia_oceanography_pncima/f63a3614-84ae-4168-b340-6919eac458c6
        https://gis-hub.ca/vector/ia-geography-pncima/ia_oceanography_pncima/f63a3614-84ae-4168-b340-6919eac458c6

        Finally, in the nginx conf inside mapserver, allow access /geoserver/gwc and /geoserver/hubdata/wms only through host gis-hub.ca.
        Geoserver admins need to use the Geoserver web interface, which is accessed directly through maps.gis-hub.ca:8080, 
        which bypasses Nginx entirely. That interface is already secured with an admin pass. 
        """

        logger.info('Check if user is admin')
        if auth.is_admin(c.user):
            # Return success immediately if user is admin
            logger.warning('User %s is admin. Skipping all auth checks' %
                           c.user)
            return render('restricted/restricted_ok_page.html')

        auth_status = False
        try:
            # Get resource object
            resource = model.Resource.get(resource_id)
            if not resource:
                logger.warning('Resource %s not found!' % resource_id)
                flask_abort(401, 'Access denied')
            resource = resource.as_dict()

            # Ensure that the Geoserver layer name requested by the user matches the resource
            # Get the internal layer name used in Geoserver, directly from metadata if exists
            gs_layer_name = resource.get('geoserver_layer')
            if gs_layer_name:
                if user_gs_layer != gs_layer_name:
                    logger.warning(
                        'User requested a layer name not from this resource! %s, expected %s'
                        % (user_gs_layer, gs_layer_name))
                    flask_abort(401, 'Invalid geoserver layer name')
            else:
                # If the geoserver_layer field does not exist, get layer name from map_preview_link field
                logger.warning(
                    'Field geoserver_layer is empty, try map_preview_link')
                match_ok = self.check_geoserver_layer_name(
                    resource, user_gs_layer)
                if not match_ok:
                    logger.warning(
                        'User requested a layer name not from this resource! %s'
                        % user_gs_layer)
                    flask_abort(401, 'Invalid geoserver layer name')

            package_id = resource.get('package_id')

            # Get package object using package_id from the resource
            package = model.Package.get(package_id)
            if not package:
                msg = 'Dataset %s not found!' % package_id
                logger.warning(msg)
                logger.warning(
                    'Were HYPHENS in the dataset name changed to UNDERSCORES?')
                flask_abort(401, msg)
            package = package.as_dict()

            logger.info(
                'Checking access for User: %s, Resource: %s, Geoserver layer: %s, Package: %s'
                % (c.user, resource.get('title'), user_gs_layer,
                   package.get('name')))

            # Ready to check access
            authorized = restricted_logic.restricted_check_user_resource_access(
                c.user, resource, package)
            auth_status = authorized.get('success')
            logger.info('Status: %s' % auth_status)

        except:
            # If anything else goes wrong, throw an error back to Nginx
            logger.warning(
                'Something went wrong during auth check. Return error 401')
            import traceback
            logger.error(traceback.format_exc())
            flask_abort(401, 'Error in auth check')

        if auth_status:
            """
            Return a normal page with status code 2xx IF the user is authorized for this resource.
            Nginx responds to a 2xx status code by passing the auth request, then continues with 
            the proxy to maps.gis-hub.ca/geoserver. 
            """
            return render('restricted/restricted_ok_page.html')
        else:
            """
            This causes the server to throw error 500, which is OK because anything other than 
            a 2xx code causes Nginx to fail the auth request, and deny the proxy. User will see an 
            error in their browser. 
            https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/
            """
            return base.abort(404, _(u'User not authorized'))