marklogic-community / roxy

Deployment tool for MarkLogic applications. Also provides optional unit test and XQuery MVC structure
Other
87 stars 66 forks source link

Support for Element Level Security #846

Open grtjn opened 6 years ago

grtjn commented 6 years ago

Involves creating query rolesets, and protected paths

grtjn commented 6 years ago

I've done things like this in app_specific, but we need a better way of feeding relevant roles and paths to protect:

  alias_method :original_bootstrap,      :bootstrap

  def bootstrap
    original_bootstrap
    bootstrap_els
  end

  def bootstrap_els
    logger.info "Bootstrapping Element Level Security.."
    r = execute_query(
      %Q{
        xquery version "1.0-ml";

        import module namespace sec = "http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy";

        let $app-name := "#{@properties['ml.app-name']}"
        let $role-names := (
          $app-name || "-power-role",
          $app-name || "-customer-A-role",
          $app-name || "-customer-B-role"
        )
        let $role-ids :=
          for $r in $role-names
          return
            xdmp:role($r)
        return (
          let $existing-rolesets :=
            collection(sec:query-rolesets-collection())
              //sec:query-roleset
                [count(sec:role-id[. = $role-ids]) eq count($role-ids)]
          where empty($existing-rolesets)
          return
            let $_ := sec:add-query-rolesets(sec:query-rolesets(sec:query-roleset($role-names)))
            return "Added roleset: ((" || string-join($role-names, ",") || "))",

          let $permissions :=
            for $role-name in $role-names
            return
              xdmp:permission($role-name, "read")
          let $protected-paths := (
            "//ip",
            "//City",
            "//Gender",
            "//TwitterID",
            "//FacebookID",
            "//InstagramID",
            "//WeiboID",
            "//EmailAddress",
            "//StreetAddress",
            "//ZipCode",
            "//TelephoneNumber",
            "//Birthday",
            "//NbOfChilds",
            "//Invoice"
          )
          for $path in $protected-paths 
          let $existing-path :=
            collection(sec:protected-paths-collection())
              [.//sec:path-expression eq $path]
          return
            if (empty($existing-path)) then
              let $_ := sec:protect-path($path, (), $permissions)
              return "Protected path: " || $path
            else
              let $existing-permissions := sec:path-get-permissions($path, ())
              where count($existing-permissions[string(.) = $permissions/string()]) ne count($permissions)
              return
                let $_ := sec:path-add-permissions($path, (), $permissions)
                return "Updated path: " || $path
        )
      },
      { :db_name => "Security" }
    )
    r.body = parse_body r.body
    logger.info ""
    logger.info r.body
    logger.info ""
  end
grtjn commented 6 years ago

Corresponding wipe:

  alias_method :original_wipe,           :wipe

  def wipe_els
    logger.info "Wiping Element Level Security.."
    r = execute_query(
      %Q{
        xquery version "1.0-ml";

        import module namespace sec = "http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy";

        let $app-name := "#{@properties['ml.app-name']}"
        let $role-names := (
          $app-name || "-power-role",
          $app-name || "-customer-A-role",
          $app-name || "-customer-B-role"
        )
        let $role-ids :=
          for $r in $role-names
          return
            xdmp:role($r)
        return (
          for $existing-roleset in
            collection(sec:query-rolesets-collection())
              /sec:query-rolesets
                [sec:role-id = $role-ids]
          return
            let $_ := sec:remove-query-rolesets($existing-roleset)
            return ("Removed roleset: ((" || string-join($existing-roleset/sec:role-id/string(), ",") || "))"),

          let $permissions :=
            for $role-name in $role-names
            return
              xdmp:permission($role-name, "read")
          let $protected-paths := (
            "//ip",
            "//City",
            "//Gender",
            "//TwitterID",
            "//FacebookID",
            "//InstagramID",
            "//WeiboID",
            "//EmailAddress",
            "//StreetAddress",
            "//ZipCode",
            "//TelephoneNumber",
            "//Birthday",
            "//NbOfChilds",
            "//Invoice"
          )
          for $path in $protected-paths
          let $existing-path :=
            collection(sec:protected-paths-collection())
              [//sec:path-expression eq $path]
          return
            if (exists($existing-path)) then
              let $existing-permissions := sec:path-get-permissions($path, ())
              return
                if (count($existing-permissions[string(.) = $permissions/string()]) eq count($permissions)) then
                  let $_ := sec:unprotect-path($path, ())
                  return
                    "Unprotecting path: " || $path (:sec:remove-path($path, ()):)
                else if ($existing-permissions) then
                  let $_ := sec:path-remove-permissions($path, (), $permissions)
                  return
                    "Unprotecting path: " || $path
                else ()
            else ()
        );
        xquery version "1.0-ml";

        import module namespace sec = "http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy";

        for $path in collection(sec:protected-paths-collection())//sec:path-expression
        let $existing-permissions := sec:path-get-permissions($path, ())
        where empty($existing-permissions)
        return
          let $_ := sec:remove-path($path, ())
          return
            "Removing path: " || $path
      },
      { :db_name => "Security" }
    )
    r.body = parse_body r.body
    logger.info ""
    logger.info r.body
    logger.info ""
  end

  def wipe
    wipe_els
    original_wipe
  end