newer pg module
[mirror/dsa-puppet.git] / 3rdparty / modules / postgresql / manifests / server / grant.pp
diff --git a/3rdparty/modules/postgresql/manifests/server/grant.pp b/3rdparty/modules/postgresql/manifests/server/grant.pp
new file mode 100644 (file)
index 0000000..f030019
--- /dev/null
@@ -0,0 +1,294 @@
+# Define for granting permissions to roles. See README.md for more details.
+define postgresql::server::grant (
+  String $role,
+  String $db,
+  Optional[String] $privilege      = undef,
+  String $object_type              = 'database',
+  Optional[String[1]] $object_name = undef,
+  String $psql_db                  = $postgresql::server::default_database,
+  String $psql_user                = $postgresql::server::user,
+  Integer $port                    = $postgresql::server::port,
+  Boolean $onlyif_exists           = false,
+  Hash $connect_settings           = $postgresql::server::default_connect_settings,
+) {
+
+  $group     = $postgresql::server::group
+  $psql_path = $postgresql::server::psql_path
+
+  if ! $object_name {
+    $_object_name = $db
+  } else {
+    $_object_name = $object_name
+  }
+
+  #
+  # Port, order of precedence: $port parameter, $connect_settings[PGPORT], $postgresql::server::port
+  #
+  if $port != undef {
+    $port_override = $port
+  } elsif $connect_settings != undef and has_key( $connect_settings, 'PGPORT') {
+    $port_override = undef
+  } else {
+    $port_override = $postgresql::server::port
+  }
+
+  ## Munge the input values
+  $_object_type = upcase($object_type)
+  $_privilege   = upcase($privilege)
+
+  ## Validate that the object type is known
+  validate_re($_object_type,[
+    #'^COLUMN$',
+    '^DATABASE$',
+    #'^FOREIGN SERVER$',
+    #'^FOREIGN DATA WRAPPER$',
+    #'^FUNCTION$',
+    #'^PROCEDURAL LANGUAGE$',
+    '^SCHEMA$',
+    '^SEQUENCE$',
+    '^ALL SEQUENCES IN SCHEMA$',
+    '^TABLE$',
+    '^ALL TABLES IN SCHEMA$',
+    '^LANGUAGE$',
+    #'^TABLESPACE$',
+    #'^VIEW$',
+    ]
+  )
+  # You can use ALL TABLES IN SCHEMA by passing schema_name to object_name
+  # You can use ALL SEQUENCES IN SCHEMA by passing schema_name to object_name
+
+  ## Validate that the object type's privilege is acceptable
+  # TODO: this is a terrible hack; if they pass "ALL" as the desired privilege,
+  #  we need a way to test for it--and has_database_privilege does not
+  #  recognize 'ALL' as a valid privilege name. So we probably need to
+  #  hard-code a mapping between 'ALL' and the list of actual privileges that
+  #  it entails, and loop over them to check them.  That sort of thing will
+  #  probably need to wait until we port this over to ruby, so, for now, we're
+  #  just going to assume that if they have "CREATE" privileges on a database,
+  #  then they have "ALL".  (I told you that it was terrible!)
+  case $_object_type {
+    'DATABASE': {
+      $unless_privilege = $_privilege ? {
+        'ALL'            => 'CREATE',
+        'ALL PRIVILEGES' => 'CREATE',
+        default          => $_privilege,
+      }
+      validate_re($unless_privilege, [ '^$', '^CREATE$','^CONNECT$','^TEMPORARY$','^TEMP$',
+        '^ALL$','^ALL PRIVILEGES$' ])
+      $unless_function = 'has_database_privilege'
+      $on_db = $psql_db
+      $onlyif_function = undef
+    }
+    'SCHEMA': {
+      $unless_privilege = $_privilege ? {
+        'ALL'            => 'CREATE',
+        'ALL PRIVILEGES' => 'CREATE',
+        default          => $_privilege,
+      }
+      validate_re($_privilege, [ '^$', '^CREATE$', '^USAGE$', '^ALL$', '^ALL PRIVILEGES$' ])
+      $unless_function = 'has_schema_privilege'
+      $on_db = $db
+      $onlyif_function = undef
+    }
+    'SEQUENCE': {
+      $unless_privilege = $_privilege ? {
+        'ALL'   => 'USAGE',
+        default => $_privilege,
+      }
+      validate_re($unless_privilege, [ '^$', '^USAGE$', '^SELECT$', '^UPDATE$', '^ALL$', '^ALL PRIVILEGES$' ])
+      $unless_function = 'has_sequence_privilege'
+      $on_db = $db
+      $onlyif_function = undef
+    }
+    'ALL SEQUENCES IN SCHEMA': {
+      validate_re($_privilege, [ '^$', '^USAGE$', '^SELECT$', '^UPDATE$', '^ALL$', '^ALL PRIVILEGES$' ])
+      $unless_function = 'custom'
+      $on_db = $db
+      $onlyif_function = undef
+
+      $schema = $object_name
+
+      $custom_privilege = $_privilege ? {
+        'ALL'            => 'USAGE',
+        'ALL PRIVILEGES' => 'USAGE',
+        default          => $_privilege,
+      }
+
+      # This checks if there is a difference between the sequences in the
+      # specified schema and the sequences for which the role has the specified
+      # privilege. It uses the EXCEPT clause which computes the set of rows
+      # that are in the result of the first SELECT statement but not in the
+      # result of the second one. It then counts the number of rows from this
+      # operation. If this number is zero then the role has the specified
+      # privilege for all sequences in the schema and the whole query returns a
+      # single row, which satisfies the `unless` parameter of Postgresql_psql.
+      # If this number is not zero then there is at least one sequence for which
+      # the role does not have the specified privilege, making it necessary to
+      # execute the GRANT statement.
+      $custom_unless = "SELECT 1 FROM (
+        SELECT sequence_name
+        FROM information_schema.sequences
+        WHERE sequence_schema='${schema}'
+          EXCEPT DISTINCT
+        SELECT object_name as sequence_name
+        FROM (
+          SELECT object_schema,
+                 object_name,
+                 grantee,
+                 CASE privs_split
+                   WHEN 'r' THEN 'SELECT'
+                   WHEN 'w' THEN 'UPDATE'
+                   WHEN 'U' THEN 'USAGE'
+                 END AS privilege_type
+            FROM (
+              SELECT DISTINCT
+                     object_schema,
+                     object_name,
+                     (regexp_split_to_array(regexp_replace(privs,E'/.*',''),'='))[1] AS grantee,
+                     regexp_split_to_table((regexp_split_to_array(regexp_replace(privs,E'/.*',''),'='))[2],E'\\s*') AS privs_split
+                FROM (
+                 SELECT n.nspname as object_schema,
+                         c.relname as object_name,
+                         regexp_split_to_table(array_to_string(c.relacl,','),',') AS privs
+                    FROM pg_catalog.pg_class c
+                         LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
+                   WHERE c.relkind = 'S'
+                         AND n.nspname NOT IN ( 'pg_catalog', 'information_schema' )
+                ) P1
+            ) P2
+        ) P3
+        WHERE grantee='${role}'
+        AND object_schema='${schema}'
+        AND privilege_type='${custom_privilege}'
+        ) P
+        HAVING count(P.sequence_name) = 0"
+    }
+    'TABLE': {
+      $unless_privilege = $_privilege ? {
+        'ALL'   => 'INSERT',
+        default => $_privilege,
+      }
+      validate_re($unless_privilege,[ '^$', '^SELECT$','^INSERT$','^UPDATE$','^DELETE$',
+        '^TRUNCATE$','^REFERENCES$','^TRIGGER$','^ALL$','^ALL PRIVILEGES$' ])
+      $unless_function = 'has_table_privilege'
+      $on_db = $db
+      $onlyif_function = $onlyif_exists ? {
+        true    => 'table_exists',
+        default => undef,
+      }
+    }
+    'ALL TABLES IN SCHEMA': {
+      validate_re($_privilege, [ '^$', '^SELECT$','^INSERT$','^UPDATE$','^DELETE$',
+        '^TRUNCATE$','^REFERENCES$','^TRIGGER$','^ALL$','^ALL PRIVILEGES$' ])
+      $unless_function = 'custom'
+      $on_db = $db
+      $onlyif_function = undef
+
+      $schema = $object_name
+
+      # Again there seems to be no easy way in plain SQL to check if ALL
+      # PRIVILEGES are granted on a table. By convention we use INSERT
+      # here to represent ALL PRIVILEGES (truly terrible).
+      $custom_privilege = $_privilege ? {
+        'ALL'            => 'INSERT',
+        'ALL PRIVILEGES' => 'INSERT',
+        default          => $_privilege,
+      }
+
+      # This checks if there is a difference between the tables in the
+      # specified schema and the tables for which the role has the specified
+      # privilege. It uses the EXCEPT clause which computes the set of rows
+      # that are in the result of the first SELECT statement but not in the
+      # result of the second one. It then counts the number of rows from this
+      # operation. If this number is zero then the role has the specified
+      # privilege for all tables in the schema and the whole query returns a
+      # single row, which satisfies the `unless` parameter of Postgresql_psql.
+      # If this number is not zero then there is at least one table for which
+      # the role does not have the specified privilege, making it necessary to
+      # execute the GRANT statement.
+      $custom_unless = "SELECT 1 FROM (
+        SELECT table_name
+        FROM information_schema.tables
+        WHERE table_schema='${schema}'
+          EXCEPT DISTINCT
+        SELECT table_name
+        FROM information_schema.role_table_grants
+        WHERE grantee='${role}'
+        AND table_schema='${schema}'
+        AND privilege_type='${custom_privilege}'
+        ) P
+        HAVING count(P.table_name) = 0"
+    }
+    'LANGUAGE': {
+      $unless_privilege = $_privilege ? {
+        'ALL'            => 'USAGE',
+        'ALL PRIVILEGES' => 'USAGE',
+        default          => $_privilege,
+      }
+      validate_re($unless_privilege, [ '^$','^CREATE$','^USAGE$','^ALL$','^ALL PRIVILEGES$' ])
+      $unless_function = 'has_language_privilege'
+      $on_db = $db
+      $onlyif_function = $onlyif_exists ? {
+        true    => 'language_exists',
+        default => undef,
+      }
+    }
+
+    default: {
+      fail("Missing privilege validation for object type ${_object_type}")
+    }
+  }
+
+  # This is used to give grant to "schemaname"."tablename"
+  # If you need such grant, use:
+  # postgresql::grant { 'table:foo':
+  #   role        => 'joe',
+  #   ...
+  #   object_type => 'TABLE',
+  #   object_name => [$schema, $table],
+  # }
+  if is_array($_object_name) {
+    $_togrant_object = join($_object_name, '"."')
+    # Never put double quotes into has_*_privilege function
+    $_granted_object = join($_object_name, '.')
+  } else {
+    $_granted_object = $_object_name
+    $_togrant_object = $_object_name
+  }
+
+  $_unless = $unless_function ? {
+      false    => undef,
+      'custom' => $custom_unless,
+      default  => "SELECT 1 WHERE ${unless_function}('${role}',
+                  '${_granted_object}', '${unless_privilege}')",
+  }
+
+  $_onlyif = $onlyif_function ? {
+    'table_exists'    => "SELECT true FROM pg_tables WHERE tablename = '${_togrant_object}'",
+    'language_exists' => "SELECT true from pg_language WHERE lanname = '${_togrant_object}'",
+    default           => undef,
+  }
+
+  $grant_cmd = "GRANT ${_privilege} ON ${_object_type} \"${_togrant_object}\" TO
+      \"${role}\""
+  postgresql_psql { "grant:${name}":
+    command          => $grant_cmd,
+    db               => $on_db,
+    port             => $port_override,
+    connect_settings => $connect_settings,
+    psql_user        => $psql_user,
+    psql_group       => $group,
+    psql_path        => $psql_path,
+    unless           => $_unless,
+    onlyif           => $_onlyif,
+    require          => Class['postgresql::server']
+  }
+
+  if($role != undef and defined(Postgresql::Server::Role[$role])) {
+    Postgresql::Server::Role[$role]->Postgresql_psql["grant:${name}"]
+  }
+
+  if($db != undef and defined(Postgresql::Server::Database[$db])) {
+    Postgresql::Server::Database[$db]->Postgresql_psql["grant:${name}"]
+  }
+}