Neil Bowers

NAME

PAUSE::Permissions - interface to PAUSE's module permissions file (06perms.txt)

SYNOPSIS

  use PAUSE::Permissions 0.08;
  
  my $pp = PAUSE::Permissions->new(max_age => '1 day');
  my $mp = $pp->module_permissions('HTTP::Client');
  
  my $owner    = $mp->owner;
  my @comaints = $mp->co_maintainers;

  my $iterator = $pp->module_iterator();
  while (my $mp = $iterator->next_module) {
    print "module = ", $mp->name, "\n";
    print "  owner = ", $mp->owner // 'none', "\n";
  }

DESCRIPTION

PAUSE::Permissions provides an interface to the 06perms.txt file produced by the Perl Authors Upload Server (PAUSE). The file records who has what permissions for every module on CPAN. The format and interpretation of this file are covered in "The 06perms.txt file" below.

By default, the module will mirror 06perms.txt from CPAN, using HTTP::Tiny to request it and store it locally What gets cached locally is actually a transformed version of 06perms.txt for easier processing.

By default it will get the file from http://www.cpan.org, but you can pass an alternate URI to the constructor:

  $perms_uri = "http://$CPAN_MIRROR/modules/06perms.txt";
  $pp = PAUSE::Permissions->new(uri => $perms_uri);

If you've already got a copy lying around, you can tell the module to use that:

  $pp = PAUSE::Permissions->new( path => '/tmp/my06perms.txt' );

Note that the file you provide this way must be in the post-processed format, and not a raw copy of 06perms.txt.

Having created an instance of PAUSE::Permissions, you can then call the module_permissions method to get the permissions for a particular module. The SYNOPSIS gives the basic usage.

Note: you should make sure you're using version 0.08 or later. PAUSE now treats package names case insensitively with respect to permissions, so this module does now as well.

Getting permissions for multiple modules

Sometimes you might want to use the module_permissions() method to get permissions for multiple modules, for example if you've built up a list of modules from elsewhere. If you're doing this, then you should set the preload attribute to a true value:

 use PAUSE::Permissions 0.12;

 my $pp = PAUSE::Permissions->new(preload => 1);
 foreach my $module_name (@long_list_of_modules) {
    my $mp = $pp->module_permissions($module_name);
    # do something with $mp (instance of PAUSE::Permissions::Module)
 }

With the preload option enabled, the permissions data for all modules will be pre-loaded into memory, making the above code much quicker, trading that off against the memory used.

This attribute was introduced in version 0.12, so you should specify the minimum version when use'ing PAUSE::Permission.

METHODS

There are only four methods you need to know: the constructor (new), getting an iterator over individual entries (entry_iterator), getting an iterator over modules (module_iterator), and module_permissions().

new

The constructor takes a hash of options:

  • cache_path: the full path to the location where you'd like PAUSE::Permissions to cache the transformed version of 06perms.txt.

  • path: your own local copy of the file, to use instead of the version in the cache_path. Note that this must be in the post-processed format for the local cache, and not the original raw format of 06perms.txt.

    The constructor will die() if the file doesn't exist, or isn't readable.

  • url: the URL for 06perms.txt; defaults to http://www.cpan.org/modules/06perms.txt

  • max_age: the expiration time for cached data, once 06perms.txt has been grabbed. The age can be specified using any format supported by Time::Duration::Parse, such '1 day', '2 minutes and 30 seconds', or '02:30:00'.

  • preload: load all module permissions data into memory, to speed up repeated calls to module_permissions(). This currently (0.12 onwards) doesn't currently affect any other methods, though it might in a future release.

So you might use the following, to get 06perms.txt from your 'local' CPAN mirror and store it somewhere of your choosing:

  $pp = PAUSE::Permissions->new(
                uri     => 'http://cpan.inode.at/modules/06perms.txt',
                cachdir => '/tmp/pause',
            );

module_iterator

This is a method that returns an instance of PAUSE::Permissions::ModuleIterator, which provides a simple mechanism for iterating over the whole permissions file, module by module:

  $pp       = PAUSE::Permissions->new();
  $iterator = $pp->module_iterator();
  
  while (my $module = $iterator->next_module) {
    print "module    = ", $module->name,           "\n";
    print "owner     = ", $module->owner,          "\n";
    print "co-maints = ", $module->co_maintainers, "\n";
  }

The next_module() method returns either an instance of PAUSE::Permissions::Module, or undef when the end of the file is reached.

entry_iterator

This is a method that returns an instance of PAUSE::Permissions::EntryIterator, which provides a simple mechanism for iterating over the whole permissions file, line by line:

  $pp       = PAUSE::Permissions->new();
  $iterator = $pp->entry_iterator();
  while (my $entry = $iterator->next) {
    print "module = ", $entry->module,     "\n";
    print "user   = ", $entry->user,       "\n";
    print "perm   = ", $entry->permission, "\n";
  }

The module method returns a module name; user returns the PAUSE id of a PAUSE user; perm is one of the three permission identifiers ('m', 'f', or 'c').

module_permissions

The module_permissions method takes a single module name, and returns an instance of PAUSE::Permissions::Module:

  $mp = $pp->module_permissions( $module_name );

Refer to the documentation for PAUSE::Permissions::Module, but the key methods are:

  • owner() returns the PAUSE id of the owner (see "The 06perms.txt file" below), or undef if there isn't a defined owner.

  • co_maintainers() returns a list of PAUSE ids, or an empty list if the module has no co-maintainers.

module_permissions() returns undef if the module wasn't found in the permissions list. If you've only just registered your new module, or only just uploaded the first release, then it might not have made it into the file yet.

can_upload

This method takes a PAUSE id and a module name, and returns true (specifically 1) if the specified user has permission to upload the specified module, otherwise false (0).

 use PAUSE::Permissions 0.13;
 my $pp = PAUSE::Permissions->new(preload => 1);
 if ($pp->can_upload('NEILB', 'Foo::Bar')) {
     # User can upload package
 }

Having permission to upload a module means that either (a) the module appears in 06perms.txt and the specified user is one of the entries, or (b) the module doesn't appear, so we assume it's not on CPAN.

There are some things you should be aware of, when interpreting this:

  • the username is handled case insensitively.

  • the module name is handled case-insensitively.

  • if the module is not in 06perms.txt then this returns true, but there is a delay between permissions being assigned by PAUSE and their appearing in 06perms.txt. Also, if you're running with a long max_age parameter, it might be a while before you see the change anyway.

  • a user might theoretically have permission to upload a module, but a specific upload might fail if the distribution doesn't have an appropriately named main module. If you're not familiar with that restriction, read this blog post.

Note: this method was introduced in version 0.13, so you should specify this as a minimum version number if you're using the method.

has_permission_for

This method takes an author's PAUSE id and an optional string which specifies what type of permission you're interested in. It will return an array ref with all package names for which the author has the specified permission.

The following example takes a PAUSE id NEILB and determines all modules that NEILB can upload:

 use PAUSE::Permissions 0.14;
 my $pp = PAUSE::Permissions->new(preload => 1);
 my $ref = $pp->has_permission_for('NEILB', 'upload');
 print "NEILB has upload permission on:\n";
 foreach my $module_name (@$ref) {
    print "  $module_name\n";
 }

There are three different permission types you can request:

  • 'upload' - ability to upload, which means co-maint or owner.

  • 'owner' - author is the owner of the package.

  • 'comaint' - author is comaint of the package but not owner.

The package names are returned in case-insensitive alphabetic order.

Note: this method was introduced in version 0.14, so you should specify this as a minimum version number if you're using the method.

The 06perms.txt file

You can find the file on CPAN:

As of October 2012 this file is 8.4M in size.

The file starts with a header, followed by one blank line, then the body. The body contains one line per module per user:

  Config::Properties,CMANLEY,c
  Config::Properties,RANDY,f
  Config::Properties,SALVA,m

Each line has three values, separated by commas:

  • The name of a module.

  • A PAUSE user id, which by convention is always given in upper case.

  • A single character that specifies what permissions the user has with respect to the module. See below.

Note that this file lists modules, not distributions. Every module in a CPAN distribution will be listed separately in this file. Modules are listed in alphabetical order, and for a given module, the PAUSE ids are listed in alphabetical order.

There are three characters that can appear in the permissions column:

  • 'm' identifies the user as the registered maintainer of the module. A module can only ever have zero or one user listed with the 'm' permission. For more details on registering a module, see 04pause.html.

  • 'f' identifies the user as the first person to upload the module to CPAN. You don't have to register a module before uploading it, and ownership in this case is first-come-first-served. A module can only ever have zero or one user listed with the 'f' permission.

  • 'c' identifies the user as a co-maintainer of the module. A module can have any number of co-maintainers.

If you first upload a module, you'll get an 'f' against you in the file. If you subsequently register the module, you'll get an 'm' against you. Internally PAUSE will have you recorded with both an 'm' and an 'f', but 06perms.txt only lists the highest precedence permission for each user.

What do the permissions mean?

  • Various places refer to the 'owner' of the module. This will be either the 'm' or 'f' permission, with 'm' taking precedence. If a module has both an 'm' and an 'f' user listed, then the 'm' user is considered the owner, and the 'f' user isn't. If a module has a user with 'f' listed, but no 'm', then the 'f' user is considered the owner.

  • If a module is listed in 06perms.txt, then only the people listed (m, f, or c) are allowed to upload (new) versions of the module. If anyone else uploads a version of the module, then the offending distribution will not be indexed: it will appear in the uploader's directory on CPAN, but won't be indexed under the module.

  • Only the owner for a module can grant co-maintainer status for a module. I.e. if you have the 'm' permission, you can always do it. If you have the 'f' permission, you can only do it if no-one else has the 'm' permission. You can grant co-maintainer status using the PAUSE web interface.

  • Regardless of your permissions, you can only remove things from CPAN that you uploaded. If you're the owner, you can't delete a version uploaded by a co-maintainer. If you weren't happy with it, you could revoke their co-maintainer status and then upload a superseding version. But we'd recommend you talk to them (first).

  • If you upload a distribution containing a number of previously unseen modules, and haven't pre-registered them, then you'll get an 'f' permission for all of the modules. Let's say you upload a second release of the distribution, which doesn't include one of the modules, and then delete the first release from CPAN (via the PAUSE web interface). After some time the module will no longer be on CPAN, but you'll still have the 'f' permission in 06perms.txt. You can free up the namespace using the PAUSE interface ("Change Permissions").

  • If your first upload of a module is a Developer Release, then you won't get permissions for the module. You don't get permissions for a module until you've uploaded a non-developer release containing the module, that was accepted for indexing.

  • If you take over maintenance of a module, then you'll generally be given the permissions of the previous maintainer. So if the previous maintainer had 'm', then you'll get 'm', and (s)he will be downgraded to 'c'. If the previous maintainer had 'f', then you'll get 'f', and the previous owner will be downgraded to 'c'.

SEE ALSO

App::PAUSE::CheckPerms checks whether all modules in (your) CPAN distributions have the same permissions.

tmpdir() in File::Spec::Functions is used to get a local directory for caching 06perms.txt.

HTTP::Tiny is used to mirror 06perms.txt from CPAN.

TODO

  • Request the file gzip'd, if we've got an appropriate module that can be used to gunzip it.

  • At construct time we currently mirror the file; should do this lazily, triggering it the first time you want a module's perms.

  • Every time you ask for a module, I scan the file from the start, then close it once I've got the details for the requested module. Would be a lot more efficient to keep the file open and start the search from there, as the file is sorted. A binary chop on the file would be much more efficient as well.

  • A command-line script.

REPOSITORY

https://github.com/neilbowers/PAUSE-Permissions

AUTHOR

Neil Bowers <neilb@cpan.org>

Thanks to Andreas König, for patiently answering many questions on how this stuff all works.

COPYRIGHT AND LICENSE

This software is copyright (c) 2012-2013 by Neil Bowers <neilb@cpan.org>.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.