VUser-Google-Api-1.0.1/0000755000175000017500000000000011570731200014374 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/0000755000175000017500000000000011570731200015142 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/0000755000175000017500000000000011570731200016206 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/Google/0000755000175000017500000000000011570731200017422 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI/0000755000175000017500000000000011570731200022442 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI/V2_0/0000755000175000017500000000000011570731200023150 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI/V2_0/EmailListEntry.pm0000644000175000017500000000233711570726507026436 0ustar rbsmithrbsmithpackage VUser::Google::ProvisioningAPI::V2_0::EmailListEntry; use warnings; use strict; use vars qw($AUTOLOAD); use Carp; our $VERSION = '0.2.0'; sub new { my $object = shift; my $class = ref($object) || $object; my $self = { 'EmailList' => shift }; bless $self, $class; return $self; } sub DESTROY { }; sub AUTOLOAD { my $self = shift; my $member = $AUTOLOAD; $member =~ s/.*:://; if (exists $self->{$member}) { $self->{$member} = $_[0] if defined $_[0]; return $self->{$member}; } else { croak "Unknown member: $member"; } } =pod =head1 NAME VUser::Google::ProvisioningAPI::V2_0::EmailListEntry - Google Provisioning API 2.0 email list entry =head1 SYNOPSIS my $entry = VUser::Google::ProvisioningAPI::V2_0::EmailListEntry->new(); $entry->EmailList('bar'); =head1 ACCESSORS =over =item EmailList =back =head1 AUTHOR Randy Smith, perlstalker at vuser dot org =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 by Randy Smith, perlstalker at vuser dot org This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. =cut 1; VUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI/V2_0/EmailListRecipientEntry.pm0000644000175000017500000000247511570726507030304 0ustar rbsmithrbsmithpackage VUser::Google::ProvisioningAPI::V2_0::EmailListRecipientEntry; use warnings; use strict; use vars qw($AUTOLOAD); use Carp; our $VERSION = '0.2.0'; sub new { my $object = shift; my $class = ref($object) || $object; my $self = { 'EmailList' => shift, 'Who' => shift }; bless $self, $class; return $self; } sub DESTROY { }; sub AUTOLOAD { my $self = shift; my $member = $AUTOLOAD; $member =~ s/.*:://; if (exists $self->{$member}) { $self->{$member} = $_[0] if defined $_[0]; return $self->{$member}; } else { croak "Unknown member: $member"; } } =pod =head1 NAME VUser::Google::ProvisioningAPI::V2_0::EmailListRecipientEntry - Google Provisioning API 2.0 email list recipient entry =head1 SYNOPSIS my $entry = VUser::Google::ProvisioningAPI::V2_0::EmailListRecipientEntry->new(); $entry->EmailList('foo'); $entry->Who('bar@baz.com'); =head1 ACCESSORS =over =item EmailList =item Who =back =head1 AUTHOR Randy Smith, perlstalker at vuser dot org =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 by Randy Smith, perlstalker at vuser dot org This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. =cut 1; VUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI/V2_0/UserEntry.pm0000644000175000017500000000612011570726507025463 0ustar rbsmithrbsmithpackage VUser::Google::ProvisioningAPI::V2_0::UserEntry; use warnings; use strict; use vars qw($AUTOLOAD); use Carp; our $VERSION = '0.2.0'; sub new { my $object = shift; my $class = ref($object) || $object; #LP: changePasswordAtNextLogin my ($user, $password, $family_name, $given_name, $quota, $email, $isSuspended, $changePasswordAtNextLogin, $hashFunctionName); if (defined $isSuspended) { $isSuspended = ($isSuspended)? '1' : '0'; } #LP: changePasswordAtNextLogin if (defined $changePasswordAtNextLogin) { $changePasswordAtNextLogin = ($changePasswordAtNextLogin)? '1' : '0'; } # This doesn't quite match the Java API but I don't really care right now. # This is much easier. Perhaps, at some point in the future, this can # be changed to match the Java API a little more. my $self = { 'User' => $user, 'Password' => $password, 'isSuspended' => $isSuspended, 'FamilyName' => $family_name, 'GivenName' => $given_name, 'Email' => $email, 'Quota' => $quota, #LP: changePasswordAtNextLogin 'changePasswordAtNextLogin' => $changePasswordAtNextLogin, 'hashFunctionName' => $hashFunctionName, }; bless $self, $class; return $self; } # Alias to match the Java API a little more sub Suspended { $_[0]->isSuspended(@_); } sub isSuspended { my $self = shift; my $suspended = shift; if (defined $suspended) { if (lc($suspended) eq 'false') { $self->{'isSuspended'} = 0; } elsif (not $suspended) { $self->{'isSuspended'} = 0; } else { $self->{'isSuspended'} = 1; } } return $self->{'isSuspended'}; } #LP: changePasswordAtNextLogin sub changePasswordAtNextLogin { my $self = shift; my $changePassword = shift; if (defined $changePassword) { if (lc($changePassword) eq 'false') { $self->{'changePasswordAtNextLogin'} = 0; } elsif (not $changePassword) { $self->{'changePasswordAtNextLogin'} = 0; } else { $self->{'changePasswordAtNextLogin'} = 1; } } return $self->{'changePasswordAtNextLogin'}; } sub DESTROY { }; sub AUTOLOAD { my $self = shift; my $member = $AUTOLOAD; $member =~ s/.*:://; if (exists $self->{$member}) { $self->{$member} = $_[0] if defined $_[0]; return $self->{$member}; } else { croak "Unknown member: $member"; } } =pod =head1 NAME VUser::Google::ProvisioningAPI::V2_0::UserEntry - Google Provisioning API 2.0 User entry =head1 SYNOPSIS my $entry = VUser::Google::ProvisioningAPI::V2_0::UserEntry->new(); $entry->User('foo'); # set the user name to 'foo' $entry->GivenName('Fred'); $entry->FamilyName('Oog'); =head1 ACCESSORS =over =item User =item Password =item isSuspended =item FamilyName =item GivenName =item Email =item Quota =back =head1 AUTHOR Randy Smith, perlstalker at vuser dot org =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 by Randy Smith, perlstalker at vuser dot org This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. =cut 1; VUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI/V2_0/NicknameEntry.pm0000644000175000017500000000245111570726507026275 0ustar rbsmithrbsmithpackage VUser::Google::ProvisioningAPI::V2_0::NicknameEntry; use warnings; use strict; use vars qw($AUTOLOAD); use Carp; our $VERSION = '0.2.0'; sub new { my $object = shift; my $class = ref($object) || $object; my $self = { 'User' => shift, 'Nickname' => shift }; bless $self, $class; return $self; } sub DESTROY { }; sub AUTOLOAD { my $self = shift; my $member = $AUTOLOAD; $member =~ s/.*:://; if (exists $self->{$member}) { $self->{$member} = $_[0] if defined $_[0]; return $self->{$member}; } else { croak "Unknown member: $member"; } } =pod =head1 NAME VUser::Google::ProvisioningAPI::V2_0::NicknameEntry - Google Provisioning API 2.0 nick name entry =head1 SYNOPSIS my $entry = VUser::Google::ProvisioningAPI::V2_0::NicknameEntry->new(); $entry->User('foo'); # set the user name to 'foo' $entry->Nickname('bar'); =head1 ACCESSORS =over =item User =item Nickname =back =head1 AUTHOR Randy Smith, perlstalker at vuser dot org =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 by Randy Smith, perlstalker at vuser dot org This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. =cut 1; VUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI/V1_0.pm0000644000175000017500000007762311570726507023542 0ustar rbsmithrbsmith#A class that encapsulates the Google Apps for Your Domain Provisioning API V1.0 #see http://code.google.com/apis/apps-for-your-domain/google_apps_provisioning_api_v1.0_reference.html #(C) 2006 Johan Reinalda, johan at reinalda dot net # #skeleton generated with h2xs -AXc -n Google::ProvisioningAPI # package VUser::Google::ProvisioningAPI::V1_0; use 5.008005; use strict; use warnings; use vars qw($VERSION); use Carp; use LWP::UserAgent qw(:strict); use HTTP::Request qw(:strict); use Encode; use XML::Simple; #I don't see the need for this - JKR #require Exporter; #NOT NEEDED FOR THIS CLASS #our @ISA = qw(Exporter AutoLoader); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use VUser::Google::ProvisioningAPI ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. #I don't see the need for this - JKR #our %EXPORT_TAGS = ( 'all' => [ qw( # #) ] ); # #our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); # #our @EXPORT = qw( # #); our $VERSION = '0.11'; our $APIVersion = '1.0'; #some constants #web agent identification use constant GOOGLEAGENT => "Google_ProvisioningAPI-perl/$VERSION"; #url for Google API token login use constant GOOGLEHOST => 'www.google.com'; use constant GOOGLETOKENURL => 'https://www.google.com/accounts/ClientLogin'; use constant MAXTOKENAGE => 24 * 60 * 60; #24 hours, see API docs #base url to the Google REST API use constant GOOGLEBASEURL => 'https://www.google.com/a/services/v1.0/'; use constant SUCCESSCODE => 'Success(2000)'; use constant FAILURECODE => 'Failure(2001)'; #some size constants use constant MAXNAMELEN => 40; use constant MAXUSERNAMELEN => 30; # Preloaded methods go here. #the constructor sub new { #parse parameters, if any (@_ == 4) || croak 'Constructor takes 3 arguments: domain, admin, adminpassword'; my $object = shift(); my $class = ref($object) || $object; my $self = { #Google related variables domain => shift(), #the Google hosted domain we are accessing admin => shift(), #the account to use when authenticating password => shift(), #the password to use when authenticating refreshtoken => 0, #if set, will force a re-authentication authtoken => '', #the authentication token returned from google authtime => 0, #time when authentication happened; only valid for 24 hours requestcontent => '', #the last http content posted to Google replyheaders => '', #the http headers of the last reply replycontent => '', #the http content of the last reply result => {}, #the resulting hash from the last reply data as parsed by XML::Simple #some other variables debug => 0, #when turned on, will spit out debug info to STDERR #some statistics that are 'read-only' stats => { ctime => time, #object creation time rtime => 0, #time of last request requests => 0, #number of API requests made success => 0, #number of successes logins => 0, #number of authentications performed } }; #return object bless( $self, 'VUser::Google::ProvisioningAPI::V1_0'); return $self; } #method used to (re)login to the API, either first time, or as token times out sub Relogin { #get object reference my $self = shift(); $self->dprint("Relogin called\n"); my $retval = 0; #adjust stats counter $self->{stats}->{logins}++; #clear last results $self->{replyheaders} = $self->{replycontent} = ''; $self->{result} = {}; # Create an LWP object to make the HTTP POST request my $lwp = LWP::UserAgent->new; if(defined($lwp)) { $lwp->agent(GOOGLEAGENT); $lwp->from($self->{admin}.'@'.$self->{domain}); # Submit the request with values for # accountType, Email and Passwd variables. my $response = $lwp->post( GOOGLETOKENURL, [ 'accountType' => 'HOSTED', 'Email' => $self->{admin}.'@'.$self->{domain}, 'Passwd' => $self->{password} ] ); #save reply page $self->{replyheaders} = $response->headers->as_string; $self->{replycontent} = $response->content; if ($response->is_success) { # Extract the authentication token from the response foreach my $line (split/\n/, $response->content) { #$self->dprint( "RECV'd: $line" ); if ($line =~ m/^SID=(.+)$/) { $self->{authtoken} = $1; $self->{authtime} = time; $self->dprint("Token found: $self->{authtoken}\n"); #clear refresh $self->{refreshtoken} = 0; $retval = 1; last; } } } else { $self->dprint("Error in login: " . $response->status_line . "\n"); $self->{result}->{reason} = "Error in login: " . $response->status_line; } } else { $self->dprint("Error getting lwp object: $!\n"); $self->{result}->{reason} = "Error getting lwp object: $!"; } return $retval; } #check if we are authenticated. If not, try to re-login sub IsAuthenticated { #get object reference my $self = shift(); if( $self->{refreshtoken} or ( (time - $self->{authtime}) > MAXTOKENAGE ) ) { return $self->Relogin(); } #we are still okay! return 1; } #generic request routine that handles most functionality #requires 3 arguments: Type, Action, Body #Type is the object type to action upon. ('Account', 'Alias', 'MailingList') #Action is what needs to be done #Body is the xml specific to the action sub Request { my $retval = 0; #get object reference my $self = shift(); $self->dprint( "***REQUEST***\n"); #clear last results $self->{replyheaders} = $self->{replycontent} = ''; $self->{result} = {}; if(@_ != 3) { $self->{result}->{reason} = 'Invalid number of arguments to request()'; return 0; } #get parameters my($type,$action,$body) = @_; $self->dprint( "Type: $type\nAction: $action\n$body\n"); #keep some stats $self->{stats}->{requests}++; $self->{stats}->{rtime} = time; #check if we are authenticated to google if(!$self->IsAuthenticated()) { $self->dprint( "Error authenticating\n"); return 0; } #standard XML pre and post segments my $pre = <<"EOL"; $type<\/hs:type> $self->{authtoken} $self->{domain} EOL my $post = ''; #create to request body $body = $pre . $body . $post; #properly encode it $body = encode('UTF-8',$body); #save the request content $self->{requestcontent} = $body; # Create an LWP object to make the HTTP POST request over my($ua) = LWP::UserAgent->new; if(!defined($ua)) { $self->dprint("Cannot create LWP::UserAgent object: $!\n"); $self->{result}->{reason} = "Cannot create LWP::UserAgent object in request(): $!"; return $retval; } #and create the request object where are we connecting to my $url = GOOGLEBASEURL . $action; $self->dprint("URL: $url\n"); my $req = HTTP::Request->new(POST => $url); if(!defined($req)) { $self->dprint("Cannot create HTTP::Request object: $!\n"); $self->{result}->{reason} = "Cannot create HTTP::Request object in request(): $!"; return $retval; } #set some user agent variables $ua->agent( GOOGLEAGENT ); $ua->from( '<' . $self->{admin}.'@'.$self->{domain} . '>'); # Submit the request with values for # accountType, Email and Passwd variables. #$req->header('ContentType' => 'application/x-www-form-urlencoded'); $req->header('Content-Type' => 'application/xml'); $req->header('Accept' => 'application/xml'); $req->header('Content-Lenght' => length($body) ); $req->header('Connection' => 'Keep-Alive'); $req->header('Host' => GOOGLEHOST); #assign the data to the request $req->content($body); #execute the request my $response = $ua->request($req); #save reply page $self->{replyheaders} = $response->headers->as_string; $self->{replycontent} = $response->content; #check result if ($response->is_success) { $self->{stats}->{success}++; $self->dprint( "Success in post:\n"); #delete all namespace elements to keep it simple (ie. remove "hs:") #this avoids the need to use XML::NameSpace my $xml = decode('UTF-8', $response->content); $xml =~ s/hs\://g; $self->dprint( $xml ); #now go parse it using XML::Simple $self->{result} = XMLin($xml,ForceArray => 0); #include Data::Dumper above if you want to use this line: #$self->dprint( Dumper($self->{result}) ); #see if this was a successful call if( defined($self->{result}->{status}) and $self->{result}->{status} eq SUCCESSCODE ) { $self->dprint("Google API success!"); $retval = 1; } else { $self->dprint("Google API failure!"); if(defined($self->{result}->{reason})) { $@ = "Google API failure: $self->{result}->{status} - $self->{result}->{reason}"; } else { $@ = "Google API failure: reason not found!"; $self->{result}->{reason} = "Google API failure: reason not found!"; } } } else { $self->dprint( "Error in post: " . $response->status_line . "\n"); $self->{result}->{reason} = "Error in http post: " . $response->status_line; } #show full response for now #$self->dprint( "Headers:\n" . $response->headers->as_string); #foreach my $line (split/\n/, $response->content) { # $self->dprint( "RECV'd: $line\n"); #} return $retval; } ###################################### ### these are the actual API calls ### ### See the Google docs for more ### ###################################### ### HOSTED ACCOUNT routines ### sub CreateAccountEmail { #get object reference my $self = shift(); $self->dprint( "CreateAccount called\n"); #check remaining arguments if(@_ < 4) { $self->dprint( "CreateAccountEmail method requires at least 4 arguments!\n"); $self->{result}->{reason} = "CreateAccountEmail method requires at least 4 arguments!"; return 0; } #get arguments my $userName = shift(); my $firstName = shift(); my $lastName = shift(); my $password = shift(); my $quota = shift() if (@_); #this one is optional my $body = <<"EOL"; $firstName $lastName $password $userName EOL if(defined($quota)) { $body .= "\t\t$quota<\/hs:quota>\n"; } #add the final end-of-section tab $body .= "\t<\/hs:CreateSection>\n"; return $self->Request('Account','Create/Account/Email',$body); } #NOTE: this API call may be discontinued! sub CreateAccount { #get object reference my $self = shift(); $self->dprint( "CreateAccount called\n"); #check remaining arguments if(@_ != 4) { $self->dprint( "CreateAccount method requires 4 arguments!\n"); $self->{result}->{reason} = "CreateAccount method requires 4 arguments!"; return 0; } #get arguments my $userName = shift(); my $firstName = shift(); my $lastName = shift(); my $password = shift(); my $body = <<"EOL"; $firstName $lastName $password $userName EOL return $self->Request('Account','Create/Account',$body); } sub UpdateAccount { #get object reference my $self = shift(); $self->dprint( "UpdateAccount called\n"); #check remaining arguments if(@_ != 4) { $self->dprint( "UpdateAccount method requires 4 arguments!\n"); $self->{result}->{reason} = "UpdateAccount method requires 4 arguments!"; return 0; } #get arguments my $userName = shift(); my $firstName = shift(); my $lastName = shift(); my $password = shift(); #build request body my $body = <<"EOL"; userName $userName EOL if(defined($firstName)) { $body .= "\t\t$firstName<\/hs:firstName>\n"; } if(defined($lastName)) { $body .= "\t\t$lastName<\/hs:lastName>\n"; } if(defined($password)) { $body .= "\t\t$password<\/hs:password>\n"; } #add the final end-of-section tab $body .= "\t<\/hs:UpdateSection>\n"; return $self->Request('Account','Update/Account',$body); } sub UpdateAccountEmail { #get object reference my $self = shift(); $self->dprint( "UpdateAccountEmail called\n"); #check remaining arguments if(@_ != 1) { $self->dprint( "UpdateAccount method requires 1 argument!\n"); $self->{result}->{reason} = "CreateAccount method requires 1 argument!"; return 0; } #get arguments my $userName = shift(); my $body = <<"EOL"; userName $userName 1 EOL return $self->Request('Account','Update/Account/Email',$body); } sub UpdateAccountStatus { #get object reference my $self = shift(); $self->dprint( "UpdateAccountStatus called\n"); #check remaining arguments if(@_ != 2) { $self->dprint( "UpdateAccount method requires 2 argument!\n"); $self->{result}->{reason} = "CreateAccount method requires 2 arguments!"; return 0; } #get arguments my $userName = shift(); my $status = shift(); if($status ne 'locked' and $status ne 'unlocked') { $self->dprint( "Error: status invalid!\n"); $self->{result}->{reason} = 'Invalid status'; return 0; } my $body = <<"EOL"; userName $userName $status EOL return $self->Request('Account','Update/Account/Status',$body); } sub RetrieveAccount { #get object reference my $self = shift(); $self->dprint( "RetrieveAccount called\n"); #check remaining arguments if(@_ != 1) { $self->dprint( "RetrieveAccount method requires 1 argument!\n"); $self->{result}->{reason} = "RetrieveAccount method requires 1 argument!"; return 0; } #get argument my $userName = shift(); my $body = <<"EOL"; userName $userName EOL return $self->Request('Account','Retrieve/Account',$body); } sub DeleteAccount { #get object reference my $self = shift(); $self->dprint( "DeleteAccount called\n"); #check remaining arguments if(@_ != 1) { $self->dprint( "DeleteAccount method requires 1 argument!\n"); $self->{result}->{reason} = "DeleteAccount method requires 1 argument!"; return 0; } #get argument my $userName = shift(); my $body = <<"EOL"; userName $userName EOL return $self->Request('Account','Delete/Account',$body); } sub RenameAccount { #This is derived from the Python sample code: #----- #Username change. Note that this feature must be explicitly # enabled by the domain administrator, and is not enabled by # default. # # Args: # oldname: user to rename # newname: new username to set for the user # alias: if 1, create an alias of oldname for newname #----- #Ie. this may not work yet - JKR 20061204 #get object reference my $self = shift(); $self->dprint( "RenameAccount called\n"); #check remaining arguments if(@_ != 3) { $self->dprint( "RenameAccount method requires 3 arguments!\n"); $self->{result}->{reason} = "RenameAccount method requires 3 arguments!"; return 0; } #get arguments my $oldName = shift(); my $newName = shift(); my $alias = shift(); #check format of alias; default to 0 $alias = lc($alias); if($alias ne '1') { $alias = '0'; } #build request format my $body = <<"EOL"; userName $oldName $newName $alias EOL return $self->Request('Account','Update/Account/Username',$body); } ### ALIAS routines ### sub CreateAlias { #get object reference my $self = shift(); $self->dprint( "CreateAlias called\n"); #check remaining arguments if(@_ != 2) { $self->dprint( "CreateAlias method requires 2 arguments!\n"); $self->{result}->{reason} = "CreateAlias method requires 2 arguments!"; return 0; } #get argument my $userName = shift(); my $alias = shift(); #create the command format my $body = <<"EOL"; $userName $alias EOL return $self->Request('Alias','Create/Alias',$body); } sub RetrieveAlias { #get object reference my $self = shift(); $self->dprint( "RetrieveAlias called\n"); #check remaining arguments if(@_ != 1) { $self->dprint( "RetrieveAlias method requires 1 argument!\n"); $self->{result}->{reason} = "RetrieveAlias method requires 1 argument!"; return 0; } #get argument my $userName = shift(); my $body = <<"EOL"; aliasName $userName EOL return $self->Request('Alias','Retrieve/Alias',$body); } sub DeleteAlias { #get object reference my $self = shift(); $self->dprint( "DeleteAlias called\n"); #check remaining arguments if(@_ != 1) { $self->dprint( "DeleteAlias method requires 1 argument!\n"); $self->{result}->{reason} = "DeleteAlias method requires 1 argument!"; return 0; } #get arguments my $alias = shift(); my $body = <<"EOL"; aliasName $alias EOL return $self->Request('Alias','Delete/Alias',$body); } ### Mailing List routines sub CreateMailingList { #get object reference my $self = shift(); $self->dprint( "CreateMailingList called\n"); #check remaining arguments if(@_ != 1) { $self->dprint( "CreateMailingList method requires 1 argument!\n"); $self->{result}->{reason} = "CreateMailingList method requires 1 argument!"; return 0; } #get arguments my $mailingListName = shift(); my $body = <<"EOL"; $mailingListName EOL return $self->Request('MailingList','Create/MailingList',$body); } sub UpdateMailingList { #get object reference my $self = shift(); $self->dprint( "UpdateMailingList called\n"); #check remaining arguments if(@_ != 3) { $self->dprint( "UpdateMailingList method requires 3 arguments!\n"); $self->{result}->{reason} = 'UpdateMailingList method requires 3 arguments!'; return 0; } #get arguments my $mailingListName = shift(); my $userName = shift(); my $listOperation = shift(); my $body = <<"EOL"; mailingListName $mailingListName $userName $listOperation EOL return $self->Request('MailingList','Update/MailingList',$body); } sub RetrieveMailingList { #get object reference my $self = shift(); $self->dprint( "RetrieveMailingList called\n"); #check remaining arguments if(@_ != 1) { $self->dprint( "RetrieveMailingList method requires 1 argument!\n"); $self->{result}->{reason} = 'RetrieveMailingList method requires 1 arguments!'; return 0; } #get argument my $mailingListName = shift(); my $body = <<"EOL"; mailingListName $mailingListName EOL return $self->Request('MailingList','Retrieve/MailingList',$body); } sub DeleteMailingList { #get object reference my $self = shift(); $self->dprint( "DeleteMailingList called\n"); #check remaining arguments if(@_ != 1) { $self->dprint( "DeleteMailingList method requires 1 argument!\n"); $self->{result}->{reason} = 'DeleteMailingList method requires 1 argument!'; return 0; } #get argument my $mailingListName = shift(); my $body = <<"EOL"; mailingListName $mailingListName EOL return $self->Request('MailingList','Delete/MailingList',$body); } ################################################################ # below are various subroutines to access local 'private' data # ################################################################ #the content of the request from and reply from Google API engine sub requestcontent { my $self = shift(); return $self->{requestcontent}; } sub replyheaders { my $self = shift(); return $self->{replyheaders}; } sub replycontent { my $self = shift(); return $self->{replycontent}; } #various access to local variables sub debug { my $self = shift(); $self-> { debug } = shift() if (@_); return $self->{debug}; } #change the admin account sub admin { my $self = shift(); if (@_) { $self-> { admin } = shift(); $self-> { refreshtoken } = 1; } return $self->{admin}; } #password can only be set, not read! sub password { my $self = shift(); if (@_) { $self-> { password } = shift(); #force authentication update on next request $self-> { refreshtoken } = 1; } return ''; } #the following can only be read! sub authtime { my $self = shift(); return $self->{authtime}; } #same for create time sub ctime { my $self = shift(); return $self->{stats}->{ctime}; } #and request time sub rtime { my $self = shift(); return $self->{stats}->{rtime}; } sub requests { my $self = shift(); return $self->{stats}->{requests}; } sub logins { my $self = shift(); return $self->{stats}->{logins}; } sub success { my $self = shift(); return $self->{stats}->{success}; } sub version { my $self = shift(); return $APIVersion; } #several helper routines #print out debugging to STDERR if debug is set sub dprint { my $self = shift(); my($text) = shift if (@_); if( $self->{debug} and defined ($text) ) { print STDERR $text . "\n"; } } 1; __END__ =pod =head1 NAME VUser::Google::ProvisioningAPI::V1_0 - Perl module that implements version 1.0 of the Google Apps for Your Domain Provisioning API =head1 SYNOPSIS use VUser::Google::ProvisioningAPI; my $google = new VUser::Google::ProvisioningAPI($domain,$admin,$password); $google->CreateAccount($userName, $firstName, $lastName, $password); $google->RetrieveAccount($userName); =head1 REQUIREMENTS VUser::Google::ProvisioningAPI requires the following modules to be installed: =over =item C =item C =item C =item C =back =head1 DESCRIPTION VUser::Google::ProvisioningAPI provides a simple interface to the Google Apps for Your Domain Provisioning API. It uses the C module for the HTTP transport, and the C module for the HTTP request and response. =head2 Examples For a complete description of the meaning of the following methods, see the Google API documentation referenced in the SEE ALSO section. #create the object $google = new Google:ProvisioningAPI($domain,$admin,$password) || die "Cannot create google object"; print 'Module version: ' . $google->VERSION . "\nAPI Version: " . $google->version() . "\n"; #create a hosted account if( $google->CreateAccount( $userName, $firstName, $lastName, $password ) ) { print "Account created!\N"; } #add email services to the account $google->UpdateAccountEmail($userName); #retrieving account data if($google->RetrieveAccount($userName)) { print 'Username: ' . $google->{result}->{RetrievalSection}->{userName} . "\n"; print 'firstName: ' . $google->{result}->{RetrievalSection}->{firstName} . "\n"; print 'lastName: ' . $google->{result}->{RetrievalSection}->{lastName} . "\n"; print 'accountStatus: ' . $google->{result}->{RetrievalSection}->{accountStatus} . "\n"; } #see what the result hash after a request looks like use Data::Dumper; print Dumper($google->{result}); #delete an account $ret = DeleteAccount($userName); #accessing the HTML data as it was received from the Google servers: print $google->replyheaders(); print $google->replycontent(); =head1 CONSTRUCTOR new ( $domain, $admin, $adminpassword ) This is the constructor for a new VUser::Google::ProvisioningAPI object. $domain is the domain name registered with Google Apps For Your Domain, $admin is an account in the above domain that has the right to manage that domain, and $adminpassword is the password for that account. Note that the constructor will NOT attempt to perform the 'ClientLogin' call to the Google Provisioning API (see below). Authentication happens automatically when the first API call is performed. The token will be remembered for the duration of the object, and will be automatically refreshed as needed. If you want to verify that you can get a valid token before performing any operations, follow the constructor with a call to IsAuthenticated() as such: print "Authentication OK\n" unless not $google->IsAuthenticated(); =head1 METHODS Below are all the methods available on the object. For the Google API specific methods, see the Google API documentation for more details. When a request is properly handled by Google's API engine, the webpost to the API succeeds. This results in a valid page being returned. The content of this page then defines whether the request succeeded or not. All pages returing the 'Success(2000)' status code will result in the API method succeeding, and returning a 1. All failures return 0. Please see the section below on how to access the result data, and how to determine the reasons for errors. If the web post fails (as determined by the C method IsSuccess() ), the method returns 0, and the {reason} hash is set to a descriptive error. You can then examine the raw data to get an idea of what went wrong. =head2 Checking Authentication IsAuthenticated() =over will check if the object has been able to authenticate with Google's api engine, and get an authentication ticket. Returns 1 if successful, 0 on failure. To see why it may fail, see the $@ variable, and the $google->{results}->{reason} hash, and parse the returned page (see the 'content' and 'header' variables.) =back =head2 Methods to Create/Retrieve/Delete =head3 'Hosted account' methods CreateAccountEmail( $userName, $firstName, $lastName, $password, $quota ) =over Creates a hosted account with email services in your domains name space. The first 4 arguments are required. The $quota argument is optional. If $quota is given, the tag will be sent with the request, otherwize is will be omitted. See the Google API docs for the API call for more details. =back CreateAccount( $userName, $firstName, $lastName, $password ) =over Creates a hosted account in your domains name space. This account does NOT have email services by default. You need to call UpdateAccountEmail() to add email services. NOTE: this API call may be discontinued! See CreateAccountEmail() for a replacement. =back UpdateAccount( $username, $firstName, $lastName, $password ) =over $username is the mandatory name of the hosted account. The remaining paramaters are optional, and can be set to 'undef' if you do not wish to change them Eg. to change the password on an account, call this as; =back UpdateAccount( $username, undef, undef, 'newpassword' ); =over to change names only, you would call it as such: =back UpdateAccount( $username, 'newfirstname', 'newlastname', undef ); UpdateAccountEmail( $userName ) =over Adds email services to a hosted account created with CreateAccount(). NOTE: this API call may be discontinued! See CreateAccountEmail() for a replacement. =back UpdateAccountStatus( $userName, $status ) =over $status is either 'locked' or 'unlocked' =back RetrieveAccount( $userName ) DeleteAccount( $userName ) RenameAccount( $oldName, $newName, $alias ) =over $alias is either '1' or '0' WARNING: this method is derived from the Python sample code provided by Google: (Ie. this may not work yet) "Username change. Note that this feature must be explicitly enabled by the domain administrator, and is not enabled by default. Args: =over oldname: user to rename newname: new username to set for the user alias: if 1, create an alias of oldname for newname" =back =back =head3 'Alias' methods CreateAlias( $userName, $alias ) RetrieveAlias( $userName ); DeleteAlias( $alias ); =head3 'Mailing List' methods CreateMailingList( $mailingListName ) UpdateMailingList( $mailingListName, $userName, $listOperation ) =over $listOperation is either 'add' or 'remove' =back RetrieveMailingList( $mailingListName ) DeleteMailingList( $mailingListName ) =head2 Methods to set/get variables After creating the object you can get/set the administrator account and set the password with these methods. Note this will cause a re-authentication next time a Google API method is called. admin( $admin ) =over set the administrative user, and will return administator username. =back password( $string ) =over set the password, returns an empty string =back =head2 Miscelleaneous statistics methods There are a few methods to access some statistics data that is collected while the object performing Google API calls. authtime() =over returns the time of last authentication, as generated by the time() function =back ctime() =over returns the create time of the object, as generated by the time() function =back rtime() =over returns the time of the most recent request, as generated by the time() function =back logins() =over returns the number of API logins that have been performed =back requests() =over returns the numbers of API requests that have been submitted to Google =back success() =over returns the numbers of successful api request performed =back And finally, version() =over returns a string with the api version implemented. This is currently '1.0' =back =head1 ACCESSING RESULTING DATA Valid return data from Google is parsed into a hash named 'result', available through the object. In this hash you can find all elements as returned by Google. This hash is produced by XML::Simple. See the Google API documentation in the SEE ALSO section for complete details. Some of the more useful elements you may need to look at are: $google->{result}->{reason} #this typically has the textual reason for a failure $google->{result}->{extendedMessage} #a more extensive description of the failure reason may be here $google->{result}->{result} #typically empty! $google->{result}->{type} #should be same of query type, eg 'Account', 'Alias', 'MailingList' The retrieval section contains data when you are querying. Here is what this section looks like when you call the RetrieveAccount method: $google->{result}->{RetrievalSection}->{firstName} $google->{result}->{RetrievalSection}->{lastName} $google->{result}->{RetrievalSection}->{accountStatus} $google->{result}->{RetrievalSection}->{aliases}->{alias} $google->{result}->{RetrievalSection}->{emailLists}->{emailList} To see the structure of the result hash, use the Data::Dumper module as such: use Data::Dumper; print Dumper($google->{result}); =head1 ACCESSING RAW GOOGLE POST AND RESULT DATA The data from the most recent post to the Google servers is available. You can access it as: print $google->requestcontent(); The most recent received HTML data is stored in two parts, the headers and the content. Both are strings. They can be accessed as such: print $google->replyheaders(); print $google->replycontent(); Note the headers are new-line separated and can easily be parsed: foreach my $headerline ( split/\n/, $g->replyheaders() ) { my ($header, $value) = split/:/, $headerline; } =head1 EXPORT None by default. =head1 SEE ALSO The official Google documentation can be found at http://code.google.com/apis/apps-for-your-domain/google_apps_provisioning_api_v1.0_reference.html For support, see the Google Group at http://groups.google.com/group/apps-for-your-domain-apis For additional support specific to this modules, email me at johan at reinalda dot net. =head1 AUTHOR Johan Reinalda, johan at reinalda dot net =head1 COPYRIGHT AND LICENSE Copyright (C) 2006 by Johan Reinalda, johan at reinalda dot net This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. If you make useful modification, kindly consider emailing then to me for inclusion in a future version of this module. =cut VUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI/V2_0.pm0000644000175000017500000011731211570726507023531 0ustar rbsmithrbsmithpackage VUser::Google::ProvisioningAPI::V2_0; use 5.008005; use warnings; use strict; #(C) 2007 Randy Smith, perlstalker at vuser dot org #(C) 2006 Johan Reinalda, johan at reinalda dot net use vars qw($VERSION); our $VERSION = '0.25'; use Carp; use LWP::UserAgent qw(:strict); use HTTP::Request qw(:strict); use Encode; use XML::Simple; use Data::Dumper; use base qw(VUser::Google::ProvisioningAPI); use VUser::Google::ProvisioningAPI::V2_0::EmailListEntry; use VUser::Google::ProvisioningAPI::V2_0::EmailListRecipientEntry; use VUser::Google::ProvisioningAPI::V2_0::NicknameEntry; use VUser::Google::ProvisioningAPI::V2_0::UserEntry; our $APIVersion = '2.0'; #some constants #web agent identification use constant GOOGLEAGENT => "Google_ProvisioningAPI-perl/0.20"; #url for Google API token login use constant GOOGLEHOST => 'www.google.com'; use constant GOOGLETOKENURL => 'https://www.google.com/accounts/ClientLogin'; use constant MAXTOKENAGE => 24 * 60 * 60; #24 hours, see API docs #base url to the Google REST API use constant GOOGLEBASEURL => 'https://www.google.com/a/feeds/'; use constant GOOGLEAPPSSCHEMA => 'http://schemas.google.com/apps/2006'; use constant SUCCESSCODE => 'Success(2000)'; use constant FAILURECODE => 'Failure(2001)'; #some size constants use constant MAXNAMELEN => 40; use constant MAXUSERNAMELEN => 30; sub DESTROY { }; # Preloaded methods go here. =pod =head1 NAME VUser::Google::ProvisioningAPI::V2_0 - Perl module that implements version 2.0 of the Google Apps for Your Domain Provisioning API =head1 SYNOPSIS use VUser::Google::ProvisioningAPI; my $google = new VUser::Google::ProvisioningAPI($domain, $admin, $passwd, '2.0'); $google->CreateUser($userName, $givenName, $familyName, $password, $quotaMB); my $user = $google->RetrieveUser($userName); =head1 REQUIREMENTS VUser::Google::ProvisioningAPI requires the following modules to be installed: =over =item C =item C =item C =item C =back =head1 DESCRIPTION VUser::Google::ProvisioningAPI provides a simple interface to the Google Apps for Your Domain Provisioning API. It uses the C module for the HTTP transport, and the C module for the HTTP request and response. =head2 Examples Adding a user: use VUser::Google::ProvisioningAPI; my $google = VUser::Google::ProvisioningAPI->new('yourdomain.com', 'admin', 'your password', '2.0'); my $entry = $google->CreateUser('joeb', 'Joe', 'Blow', 'joespassword'); if (defined $entry) { print $entry->User, " created\n"; } else { die "Add failed: ".$google->{result}{reason}; } Updating a user: my $new_entry = VUser::Google::ProvisioningAPI::V2_0::UserEntry->new(); $new_entry->Password('heresmynewpassword'); $new_entry->GivenName('Joseph'); my $entry = $google->UpdateUser('joeb', $new_entry); Delete a user: my $rc = $google->DeleteUser('joeb'); if (not $rc) { die "Can't delete user: ".$google->{result}{reason}; } =head1 CONSTRUCTOR new ($domain, $admin, $adminpasswd) This is the constructor for a new VUser::Google::ProvisioningAPI object. $domain is the domain name registered with Google Apps For Your Domain, $admin is an account in the above domain that has the right to manage that domain, and $adminpassword is the password for that account. Note that the constructor will NOT attempt to perform the 'ClientLogin' call to the Google Provisioning API (see below). Authentication happens automatically when the first API call is performed. The token will be remembered for the duration of the object, and will be automatically refreshed as needed. If you want to verify that you can get a valid token before performing any operations, follow the constructor with a call to IsAuthenticated() as such: print "Authentication OK\n" unless not $google->IsAuthenticated(); =cut sub new { #parse parameters, if any (@_ == 4) || croak 'Constructor takes 3 arguments: domain, admin, adminpassword'; my $object = shift(); my $class = ref($object) || $object; my $self = { #Google related variables domain => shift(), #the Google hosted domain we are accessing admin => shift(), #the account to use when authenticating password => shift(), #the password to use when authenticating refreshtoken => 0, #if set, will force a re-authentication authtoken => '', #the authentication token returned from google authtime => 0, #time when authentication happened; only valid for 24 hours requestcontent => '', #the last http content posted to Google replyheaders => '', #the http headers of the last reply replycontent => '', #the http content of the last reply result => {}, #the resulting hash from the last reply data as parsed by XML::Simple #some other variables debug => 0, #when turned on, will spit out debug info to STDERR #some statistics that are 'read-only' stats => { ctime => time, #object creation time rtime => 0, #time of last request requests => 0, #number of API requests made success => 0, #number of successes logins => 0, #number of authentications performed } }; #return object bless( $self, 'VUser::Google::ProvisioningAPI::V2_0'); return $self; } =pod =head1 METHODS Below are all the methods available on the object. For the Google API specific methods, see the Google API documentation for more details. When a request is properly handed by Google's API engine, the results of the action are returned as the content of the request. If the request fails (as determined by the C method is_success()), it could mean a couple of things. If it's a failure within the Google API, the content will contain an XML encoded error message. All other HTTP errors are still possible. =head2 Checking Authentication IsAuthenticated() =over will check if the object has been able to authenticate with Google's api engine, and get an authentication ticket. Returns 1 if successful, 0 on failure. To see why it may fail, see the $@ variable, and the $google->{results}->{reason} hash, and parse the returned page (see the 'content' and 'header' variables.) =back =cut #check if we are authenticated. If not, try to re-login sub IsAuthenticated { #get object reference my $self = shift(); if( $self->{refreshtoken} or ( (time - $self->{authtime}) > MAXTOKENAGE ) ) { return $self->Relogin(); } #we are still okay! return 1; } =pod Relogin() =over Performs a login if required. Relogin() will be called but the API methods and IsAuthenticated(). You should not need to call this directly. =back =cut #method used to (re)login to the API, either first time, or as token times out sub Relogin { #get object reference my $self = shift(); $self->dprint("Relogin called\n"); my $retval = 0; #adjust stats counter $self->{stats}->{logins}++; #clear last results $self->{replyheaders} = $self->{replycontent} = ''; $self->{result} = {}; # Create an LWP object to make the HTTP POST request my $lwp = LWP::UserAgent->new; if(defined($lwp)) { $lwp->agent(GOOGLEAGENT); $lwp->from($self->{admin}.'@'.$self->{domain}); # Submit the request with values for # accountType, Email and Passwd variables. my $response = $lwp->post( GOOGLETOKENURL, [ 'accountType' => 'HOSTED', 'Email' => $self->{admin}.'@'.$self->{domain}, 'Passwd' => $self->{password}, 'service' => 'apps' ] ); #save reply page $self->{replyheaders} = $response->headers->as_string; $self->{replycontent} = $response->content; if ($response->is_success) { # Extract the authentication token from the response foreach my $line (split/\n/, $response->content) { #$self->dprint( "RECV'd: $line" ); if ($line =~ m/^Auth=(.+)$/) { $self->{authtoken} = $1; $self->{authtime} = time; $self->dprint("Token found: $self->{authtoken}\n"); #clear refresh $self->{refreshtoken} = 0; $retval = 1; last; } } } else { $self->dprint("Error in login: " . $response->status_line . "\n"); $self->{result}->{reason} = "Error in login: " . $response->status_line; } } else { $self->dprint("Error getting lwp object: $!\n"); $self->{result}->{reason} = "Error getting lwp object: $!"; } return $retval; } #generic request routine that handles most functionality #requires 3 arguments: Method, URL, Body #Method is the HTTP method to use. ('GET', 'POST', etc) #URL is the API URL to talk to. #Body is the xml specific to the action. # This is not used on 'GET' or 'DELETE' requests. sub Request { my $retval = 0; #get object reference my $self = shift(); $self->dprint( "***REQUEST***\n"); #clear last results $self->{replyheaders} = $self->{replycontent} = ''; $self->{result} = {}; if(@_ != 2 and @_ != 3) { $self->{result}->{reason} = 'Invalid number of arguments to request()'; return 0; } #get parameters my($method,$url,$body) = @_; #$self->dprint( "Type: $type\nAction: $action\n$body\n"); $self->dprint("Method: $method; URL: $url\n"); $self->dprint("Body: $body\n") if $body; #keep some stats $self->{stats}->{requests}++; $self->{stats}->{rtime} = time; #check if we are authenticated to google if(!$self->IsAuthenticated()) { $self->dprint( "Error authenticating\n"); return 0; } #standard XML pre and post segments # TODO: this changes in 2.0 #properly encode it $body = encode('UTF-8',$body); #save the request content $self->{requestcontent} = $body; # Create an LWP object to make the HTTP POST request over my($ua) = LWP::UserAgent->new; if(!defined($ua)) { $self->dprint("Cannot create LWP::UserAgent object: $!\n"); $self->{result}->{reason} = "Cannot create LWP::UserAgent object in request(): $!"; return $retval; } #and create the request object where are we connecting to # v2.0 uses a diffent url based what's being done. # The API methods will construct the URL becuase action specific # information, such as domain and user, is embedded with it. # v2.0 use different methods depending on the action # It's up to the API methods to know which method to use my $req = HTTP::Request->new($method => $url); if(!defined($req)) { $self->dprint("Cannot create HTTP::Request object: $!\n"); $self->{result}->{reason} = "Cannot create HTTP::Request object in request(): $!"; return $retval; } #set some user agent variables $ua->agent( GOOGLEAGENT ); $ua->from( '<' . $self->{admin}.'@'.$self->{domain} . '>'); # Submit the request $req->header('Accept' => 'application/atom+xml'); $req->header('Content-Type' => 'application/atom+xml'); if ($body) { $req->header('Content-Length' => length($body) ); } $req->header('Connection' => 'Keep-Alive'); $req->header('Host' => GOOGLEHOST); $req->header('Authorization' => 'GoogleLogin auth='.$self->{authtoken}); #assign the data to the request # Perhaps if $method eq 'GET' or 'DELETE' would be better if ($body) { $req->content($body); } #$self->dprint(Data::Dumper::Dumper($req)); #execute the request my $response = $ua->request($req); $self->dprint(Data::Dumper::Dumper($response)); #save reply page $self->{replyheaders} = $response->headers->as_string; $self->{replycontent} = $response->content; #check result if ($response->is_success) { $self->{stats}->{success}++; $self->dprint( "Success in post:\n"); #delete all namespace elements to keep it simple (ie. remove "hs:") #this avoids the need to use XML::NameSpace # v2.0 uses a couple namespaces now, instead of just one. # I'm not sure that we can avoid using XML::NameSpace my $xml = decode('UTF-8', $response->content); #$xml =~ s/hs\://g; $self->dprint( $xml ); if ($xml) { #now go parse it using XML::Simple my $simple = XML::Simple->new(ForceArray => 1); #my $parser = XML::SAX::ParserFactory->new(Handler => $simple); #$self->{result} = $parser->parse_string($xml); $self->{result} = $simple->XMLin($xml); # (OLD) $self->{result} = XMLin($xml,ForceArray => 0); #include Data::Dumper above if you want to use this line: $self->dprint( Dumper($self->{result}) ); } else { $self->{result} = {}; } $self->dprint("Google API success!"); $retval = 1; } else { # OK. Funky issue. When trying to get a user that doesn't exist, # Google throws a 400 error instead of returning a error document. # Google has fun. If there is a problem with the request, # google triggers a 400 error witch then fails on ->is_success. # So, we need to check the content anyway to see if there is a # reason for the failure. $self->dprint("Google API failure!"); my $xml = decode('UTF-8', $response->content); $self->dprint( $xml ); if ($xml) { my $simple = XML::Simple->new(ForceArray => 1); $self->{result} = $simple->XMLin($xml); $self->dprint( 'Error result: '.Dumper($self->{result}) ); } if (defined ($self->{result}{error}[0]{reason})) { $@ = "Google API failure: " .$self->{result}{error}[0]{errorCode}.' - ' .$self->{result}{error}[0]{reason}; $self->dprint("$@\n"); $self->{result}->{reason} = $@; } else { $@ = "Google API failure: reason not found!"; $self->dprint( "Error in post: " . $response->status_line . "\n"); $self->{result}->{reason} = "Error in http post: " . $response->status_line; } } #show full response for now #$self->dprint( "Headers:\n" . $response->headers->as_string); #foreach my $line (split/\n/, $response->content) { # $self->dprint( "RECV'd: $line\n"); #} return $retval; } =pod =head2 User Methods These are the acutual API calls. These calls match up with the client library methods described for the .Net and Java libraries. =cut ### HOSTED ACCOUNT routines ### =pod CreateUser($userName, $givenName, $familyName, $password, $quota, $forceChange, $hashName) =over Creates a user in your Google Apps domain. The first four arguments are required. The C<$quota> argument is optional and may not do anything unless your agreement with Google allows you to change quotas. If C<$forceChange> is true, the user will be required to change their password after log in. C<$hashName>, if set, must be I or I. CreateUser() returns a C object if the request was successful and C otherwise. =back =cut sub CreateUser { my $self = shift; if (@_ < 4 and @_ > 7) { $self->dprint("CreateUser method requires 4 to 7 arguments\n"); $self->{result}->{reason} = "CreateUser method requires 4 to 7 arguments"; return undef; } my ($username, $given_name, $family_name, $password, $quotaMB, $forceChange, $hash_name) = @_; $forceChange = $forceChange? 1 : 0; if(defined $hash_name) { if(lc($hash_name) eq "sha-1") { $hash_name = "SHA-1"; } elsif (lc($hash_name) eq 'md5') { $hash_name = "MD5"; } else { # Unset $hash_name if it's not a valid hash type $hash_name = undef; } } my $body = $self->XMLPrefix; #LP:changePasswordAtNextLogin (todo) $body .= ''; $body .= "" if defined $quotaMB; $body .= ""; $body .= $self->XMLPostfix; if ($self->Request('POST', GOOGLEBASEURL.$self->{domain}."/user/$APIVersion", $body)) { my $entry = $self->buildUserEntry(); return $entry; } else { return undef; } # Return UserEntry } =pod RetrieveUser($userName) =over Get the passed user from Google. Returns a C object. =back =cut sub RetrieveUser { my $self = shift; if (@_ != 1) { $self->dprint("RetrieveUser method requires 1 argument\n"); $self->{result}->{reason} = "RetrieveUser method requires 1 argument"; return undef; } my $username = shift; my $url = GOOGLEBASEURL.$self->{domain}."/user/$APIVersion/$username"; if ($self->Request('GET',$url)) { return $self->buildUserEntry(); } else { return undef; } # Return UserEntry } =pod RetrieveAllUsers() =over Returns a list of all users in your domain. The entries are C objects. =back =cut sub RetrieveAllUsers { my $self = shift; # Need to deal with google's pagination thing. my $last_page = 0; my $url = GOOGLEBASEURL.$self->{domain}."/user/$APIVersion"; my @entries = (); while (not $last_page) { # It might be better to adjust this to use RetrievePageOfUsers() if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildUserEntry($entry); } } else { # There was some sort of error which caused the lookup to fail. # This also means that if pages beyond the first fail, the entire # dataset is discarded. return undef; } $last_page = 1; # gets reset to 0 if there are more pages # Look through the links to see if there's another page. # A link with rel=next means that we have another page to look at. # # TODO: May be more efficient with a last; in the else but # I had problems with infinite loops while trying to get it # sorted out. foreach my $link (@{ $self->{result}{'link'} }) { if ($link->{'rel'} eq 'next') { $url = $link->{'href'}; $last_page = 0; # } else { # $last_page = 1; } } } return @entries; # Return list of UserEntries } =pod RetrievePageOfUsers($startUser) =over Google Provisioning API 2.0 supports returning lists of users 100 at a time. C<$startUser> is optional. When used, it will be the list will start at that user. Otherwise, it will return the first 100 users. RetrievePageOfUsers() returns a list of C objects. =back =cut sub RetrievePageOfUsers { my $self = shift; if (@_ > 1) { $self->dprint("RetrievePageOfUser method requires 0 or 1 argument\n"); $self->{result}->{reason} = "RetrievePageOfUser method requires 0 or 1 argument"; return undef; } my $start_username = shift; my $url = GOOGLEBASEURL.$self->{domain}."/user/$APIVersion"; $url .= "?startUsername=$start_username" if defined $start_username; my @entries = (); if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildUserEntry($entry); } } else { # There was some sort of error which caused the lookup to fail. # This also means that if pages beyond the first fail, the entire # dataset is discarded. return undef; } # Return list of UserEntries return @entries; } =pod UpdateUser($userName, $newUserEntry) =over C<$userName> is the mandatory name of the user account. C<$newUserEntry> is a C object with the changes to the account. You only need to set the elements of C<$newUserEntry> that are being changed. B According to the Google API docs, you should not set the password unless you are actually changing the password. =back =cut sub UpdateUser { my $self = shift; if (@_ != 2) { $self->dprint("UpdateUser method requires 2 arguments\n"); $self->{result}->{reason} = "UpdateUser method requires 2 arguments"; return undef; } my $username = shift; my $new_entry = shift; # G::P::V2_0::UserEntry my $body = $self->XMLPrefix; $body .= ''; if (defined ($new_entry->User) or defined ($new_entry->Password) or defined ($new_entry->isSuspended) or defined ($new_entry->changePasswordAtNextLogin) ) { $body .= '{hashFunctionName}) { $body .= ' hashFunctionName="'.$new_entry->{hashFunctionName}.'"'; } $body .= ' userName="'.$new_entry->User.'"' if defined $new_entry->User; if (defined $new_entry->Password) { my $passwd = $new_entry->Password; # escape quotes # See section 2.4 of http://www.w3.org/TR/xml/ #$passwd =~ s/\"/\\"/; $passwd =~ s/\"/"/; $body .= ' password="'.$passwd.'"'; } $body .= ' suspended="'.($new_entry->isSuspended? 'true' : 'false').'"'; #LP:changePasswordAtNextLogin #print "too(".$new_entry->changePasswordAtNextLogin.")"; $body .= ' changePasswordAtNextLogin="'.($new_entry->changePasswordAtNextLogin? 'true' : 'false').'"'; $body .= '/>'; } if (defined ($new_entry->FamilyName) or defined ($new_entry->GivenName)) { $body .= 'FamilyName.'"' if defined $new_entry->FamilyName; $body .= ' givenName="'.$new_entry->GivenName.'"' if defined $new_entry->GivenName; $body .= '/>'; } if (defined ($new_entry->Quota)) { $body .= ''; } $body .= $self->XMLPostfix; # The body has been contructed. We are 'Go' to make the request. if ($self->Request('PUT', GOOGLEBASEURL.$self->{domain}."/user/$APIVersion/$username", $body)) { my $entry = $self->buildUserEntry(); return $entry; } else { return undef; } # Return UserEntry } =pod SuspendUser($userName) =over C<$userName> is the name of the user that you want to suspend. Returns a C object if successful. =back =cut sub SuspendUser { my $self = shift; my $username = shift; my $entry = VUser::Google::ProvisioningAPI::V2_0::UserEntry->new(); $entry->isSuspended(1); return $self->UpdateUser($username, $entry); # Return UserEntry } =pod RestoreUser($userName) =over Unsuspend the user's account. C<$userName> is required. Returns a C object if successful. =back =cut sub RestoreUser { my $self = shift; my $username = shift; my $entry = VUser::Google::ProvisioningAPI::V2_0::UserEntry->new(); $entry->isSuspended(0); return $self->UpdateUser($username, $entry); # Return UserEntry } =pod DeleteUser($userName) =over C<$userName> is the required user name to delete. Returns '1' on success. =back =cut sub DeleteUser { my $self = shift; if (@_ != 1) { $self->dprint("DeleteUser method requires 1 argument\n"); $self->{result}->{reason} = "DeleteUser method requires 1 argument"; return undef; } my $username = shift; if ($self->Request('DELETE', GOOGLEBASEURL.$self->{domain}."/user/$APIVersion/$username")) { return 1; } else { return undef; } # Return undef } ### NICKNAME routines ### =pod =head3 Nickname methods CreateNickname($userName, $nickName) =over Creates a nickname (or alias) for a user. C<$userName> is the existing user and C<$nickName> is the user's new nickname. Returns a C object on success. =back =cut sub CreateNickname { my $self = shift; if (@_ != 2) { $self->dprint("CreateNickname method requires 2 arguments\n"); $self->{result}->{reason} = "CreateNickname method requires 2 arguments"; return undef; } my $username = shift; my $nickname = shift; my $body = $self->XMLPrefix; $body .= ''; $body .= ""; $body .= ""; $body .= $self->XMLPostfix; if ($self->Request('POST', GOOGLEBASEURL.$self->{domain}."/nickname/$APIVersion", $body)) { return $self->buildNicknameEntry(); } else { return undef; } # Return NicknameEntry } =pod RetrieveNickname($nickName) =over Returns a C if the C<$nickName> exists. =back =cut sub RetrieveNickname { my $self = shift; if (@_ != 1) { $self->dprint("RetrieveNickname method requires 1 argument\n"); $self->{result}->{reason} = "RetrieveNickname method requires 1 argument"; return undef; } my $nickname = shift; if ($self->Request('GET', GOOGLEBASEURL.$self->{domain}."/nickname/$APIVersion/$nickname")) { return $self->buildNicknameEntry(); } else { return undef; } # Return NicknameEntry } =pod RetrieveNicknames($userName) =over Get all nicknames for C<$userName>. Returns a list of C objects. =back =cut sub RetrieveNicknames { my $self = shift; if (@_ != 1) { $self->dprint("RetrieveNicknames method requires 1 argument\n"); $self->{result}->{reason} = "RetrieveNicknames method requires 1 argument"; return undef; } my $username = shift; my $url = GOOGLEBASEURL.$self->{domain}."/nickname/$APIVersion?username=$username"; my $last_page = 0; my @entries = (); # And we get to deal with funky pagination here, too. while (not $last_page) { if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildNicknameEntry($entry); } } else { return undef; } # Look through the links to see if there's another page. # A link with rel=next means that we have another page to look at. foreach my $link (@{ $self->{result}{'link'} }) { if ($link->{'rel'} eq 'next') { $url = $link->{'href'}; $last_page = 0; } else { $last_page = 1; } } } return @entries; # Return list of NicknameEntries } =pod RetrieveAllNicknames() =over Get all of the nick names for your domain. Returns a list of C objects. =back =cut sub RetrieveAllNicknames { my $self = shift; my $url = GOOGLEBASEURL.$self->{domain}."/nickname/$APIVersion"; my $last_page = 0; my @entries = (); # And we get to deal with funky pagination here, too. while (not $last_page) { if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildNicknameEntry($entry); } } else { return undef; } # Look through the links to see if there's another page. # A link with rel=next means that we have another page to look at. foreach my $link (@{ $self->{result}{'link'} }) { if ($link->{'rel'} eq 'next') { $url = $link->{'href'}; $last_page = 0; } else { $last_page = 1; } } } return @entries; # Return list of NicknameEntries } =pod RetrievePageOfNicknames($startNick) =over Get 100 of the nick names for your domain. If C<$startNick> is defined, the list will start with that nick name, otherwise, the first 100 nicks will be returned. Returns a list of C objects. =back =cut sub RetrievePageOfNicknames { my $self = shift; my $start_nick = shift; my $url = GOOGLEBASEURL.$self->{domain}."/nickname/$APIVersion"; $url .= "?startNickname=$start_nick" if defined $start_nick; my @entries = (); if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildNicknameEntry($entry); } } else { return undef; } return @entries; # Return list of NicknameEntries } =pod DeleteNickname($nickName) =over Delete C<$nickName> from your domain. Returns 1 if the request succeeds. =back =cut sub DeleteNickname { my $self = shift; if (@_ != 1) { $self->dprint("DeleteNickname method requires 1 argument\n"); $self->{result}->{reason} = "DeleteNickname method requires 1 argument"; return undef; } my $nickname = shift; if ($self->Request('DELETE', GOOGLEBASEURL.$self->{domain}."/nickname/$APIVersion/$nickname")) { return 1; } else { return undef; } # Return undef } ### EMAIL LIST routines ### =pod =head3 Email list methods CreateEmailList($listName) =over Create an email list named C<$listName>. Returns a C on success. =back =cut sub CreateEmailList { my $self = shift; if (@_ != 1) { $self->dprint("CreateEmailList method requires 1 argument\n"); $self->{result}->{reason} = "CreateEmailList method requires 1 argument"; return undef; } my $emaillist = shift; my $body = $self->XMLPrefix; $body .= ''; $body .= ""; $body .= $self->XMLPostfix; if ($self->Request('POST', GOOGLEBASEURL.$self->{domain}."/emailList/$APIVersion", $body)) { my $entry = $self->buildEmailListEntry(); return $entry; } else { return undef; } # Return EmailListEntry } =pod RetrieveEmailLists($recipient) =over Get a list of all local email lists that C<$recipient> is subscribed to. C<$recipient> is limited to users at your domain. Returns a list of C objects. =back =cut sub RetrieveEmailLists { my $self = shift; if (@_ != 1) { $self->dprint("RetrieveEmailLists method requires 1 argument\n"); $self->{result}->{reason} = "RetrieveEmailLists method required 1 argument\n"; } my $recipient = shift; my $url = GOOGLEBASEURL.$self->{domain}."/emailList/$APIVersion?recipient=$recipient"; my $last_page = 0; my @entries = (); # Work with Google's pagination while (not $last_page) { if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildEmailListEntry($entry); } } else { return undef; } # Look for next page link foreach my $link (@{ $self->{result}{'link'} }) { if ($link->{'rel'} eq 'next') { $url = $link->{'href'}; $last_page = 0; } else { $last_page = 1; } } } # Return list of EmailListEntries return @entries; } =pod RetrieveAllEmailLists() =over Get a list of all email lists for your domain. Returns a list of C objects. =back =cut sub RetrieveAllEmailLists { my $self = shift; my $url = GOOGLEBASEURL.$self->{domain}."/emailList/$APIVersion"; my $last_page = 0; my @entries = (); # Work with Google's pagination while (not $last_page) { if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildEmailListEntry($entry); } } else { return undef; } # Look for next page link foreach my $link (@{ $self->{result}{'link'} }) { if ($link->{'rel'} eq 'next') { $url = $link->{'href'}; $last_page = 0; } else { $last_page = 1; } } } # Return list of EmailListEntries return @entries; } =pod RetrievePageOfEmailLists($startList) =over Get a single page (100 lists) of email lists. =back =cut sub RetrievePageOfEmailLists { my $self = shift; my $start_emaillist = shift; my $url = GOOGLEBASEURL.$self->{domain}."/emailList/$APIVersion"; if ($start_emaillist) { $url .= "?startEmailListName=$start_emaillist"; } my @entries = (); if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildEmailListEntry($entry); } } else { return undef; } # Return list of EmailListEntries return @entries; } =pod DeleteEmailList($emailList) =over Delete C<$emailList> from your domain. Returns 1 on success. =back =cut sub DeleteEmailList { my $self = shift; if (@_ != 1) { $self->dprint("DeleteUser method requires 1 argument\n"); $self->{result}->{reason} = "DeleteUser method requires 1 argument"; return undef; } my $emaillist = shift; if ($self->Request('DELETE', GOOGLEBASEURL.$self->{domain}."/emailList/$APIVersion/$emaillist")) { return 1; } else { return undef; } # Return undef } =pod AddRecipientToEmailList($recipient, $emailList) =over Adds a recipient to a mail list. C<$recipient> is the address you want to add and C<$emailList> is the list to add to. Returns a C object on success. =back =cut sub AddRecipientToEmailList { my $self = shift; if (@_ != 2) { $self->dprint("AddRecipientToEmailList method requires 2 argument\n"); $self->{result}->{reason} = "AddRecipientToEmailList method requires 2 argument"; return undef; } my $recipient = shift; my $emaillist = shift; my $body = $self->XMLPrefix; $body =~ s!>$! xmlns:gd="http://schemas.google.com/g/2005">!; $body .= ''; $body .= ""; $body .= $self->XMLPostfix; if ($self->Request('POST', GOOGLEBASEURL.$self->{domain} ."/emailList/$APIVersion/$emaillist/recipient", $body)) { my $entry = $self->buildEmailListRecipientEntry(); return $entry; } else { return undef; } # Return EmailListRecipientEntry } =pod RetrieveAllRecipients($emailList) =over Get a list of the recipients of the specified email list. Returns a list of C objects. =back =cut sub RetrieveAllRecipients { my $self = shift; if (@_ != 1) { $self->dprint("RetrieceAllRecipients method requires 1 argument\n"); $self->{result}->{reason} = "RetrieveAllRecipients method requires 1 argument"; return undef; } my $emaillist = shift; my $url = GOOGLEBASEURL.$self->{domain}."/emailList/$APIVersion/$emaillist/recipient"; my $last_page = 0; my @entries = (); # Google Pagination again while (not $last_page) { if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { my $entry = $self->buildEmailListRecipientEntry($entry); push @entries, $entry if $entry; } } else { return undef; } foreach my $link (@{ $self->{result}{'link'} }) { if ($link->{'rel'} eq 'next') { $url = $link->{'href'}; $last_page = 0; } else { $last_page = 1; } } } # Return list of EmailListRecipientEntries return @entries; } =pod RetrievePageOfRecipients($emailList, $startRecpt) =over Get a page of recipients for that given list (C<$emailList)> starting with C<$startRecpt> or the beginning if C<$startRecpt> is not defined. Returns a list of C objects. =back =cut sub RetrievePageOfRecipients { my $self = shift; if (@_ != 2) { $self->dprint("RetrievePageOfRecipients method requires 2 arguments\n"); $self->{result}->{reason} = "RetrievePageOfRecipients method requires 2 arguments"; return undef; } my $emaillist = shift; my $start_rcpt = shift; my $url = GOOGLEBASEURL.$self->{domain}."/emailList/$APIVersion/$emaillist/recipient"; if ($start_rcpt) { $url .= "?startRecipient=$start_rcpt"; } my @entries = (); if ($self->Request('GET', $url)) { foreach my $entry (@{ $self->{result}{'entry'} }) { push @entries, $self->buildEmailListRecipientEntry(); } } else { return undef; } # Return list of EmailListRecipientEntries return @entries; } =pod RemoveRecipientFromEmailList($recipient, $emailList) =over Remove C<$recipient> from the given email list (C<$emailList>). Returns 1 in success. =back =cut sub RemoveRecipientFromEmailList { my $self = shift; if (@_ != 2) { $self->dprint("RemoveRecipientFromEmailList method requires 2 arguments\n"); $self->{result}->{reason} = "RemoveRecipientFromEmailList method requires 2 arguments"; return undef; } my $recipient = shift; my $emaillist = shift; if ($self->Request('DELETE', GOOGLEBASEURL.$self->{domain} ."/emailList/$APIVersion/$emaillist/recipient/$recipient")) { return 1; } else { return undef; } # Return undef } ### Private methods sub XMLPrefix { my $pre = ''; $pre .= ''; return $pre; } sub XMLPostfix { return ''; } sub buildUserEntry { my $self = shift; my $xml = shift || $self->{result}; my $entry = VUser::Google::ProvisioningAPI::V2_0::UserEntry->new(); $entry->User($xml->{'apps:login'}[0]{'userName'}); if ($xml->{'apps:login'}[0]{'suspended'}) { if ($xml->{'apps:login'}[0]{'suspended'} eq 'true') { $entry->isSuspended(1); } else { $entry->isSuspended(0); } } #LP: changePasswordAtNextLogin if ($xml->{'apps:login'}[0]{'changePasswordAtNextLogin'}) { if ($xml->{'apps:login'}[0]{'changePasswordAtNextLogin'} eq 'true') { $entry->changePasswordAtNextLogin(1); } else { $entry->changePasswordAtNextLogin(0); } } $entry->FamilyName($xml->{'apps:name'}[0]{'familyName'}); $entry->GivenName($xml->{'apps:name'}[0]{'givenName'}); $entry->Quota($xml->{'apps:quota'}[0]{'limit'}); return $entry; } sub buildNicknameEntry { my $self = shift; my $xml = shift || $self->{result}; my $entry = VUser::Google::ProvisioningAPI::V2_0::NicknameEntry->new(); $entry->User($xml->{'apps:login'}[0]{'userName'}); # Odd parser problem: # # yeilds: # 'apps:nickname' => { 'test1' => {} }, #$entry->Nickname($xml->{'apps:nickname'}[0]{'name'}); # This is an exceptionally ugly hack to work around the parser issue # above. $entry->Nickname((keys %{$xml->{'apps:nickname'}})[0]); return $entry; } sub buildEmailListEntry { my $self = shift; my $xml = shift || $self->{'result'}; my $entry = VUser::Google::ProvisioningAPI::V2_0::EmailListEntry->new(); # This seems to have the same problem as nicknames. #$entry->EmailList($xml->{'apps:emailList'}[0]{'name'}); $entry->EmailList((keys %{$xml->{'apps:emailList'}})[0]); return $entry; } sub buildEmailListRecipientEntry { my $self = shift; my $xml = shift || $self->{'result'}; my $entry = VUser::Google::ProvisioningAPI::V2_0::EmailListRecipientEntry->new(); $entry->Who($xml->{'gd:who'}[0]{'email'}); return $entry; } =pod =head1 ACCESSING RESULTING DATA Most API calls return an object so that you don't have to screw around with the XML data. The parsed XML (by XML::Simple) is available in C<$google->{result}>. =head1 EXPORT None by default. =head1 SEE ALSO The perldocs for VUser::Google::ProvisioningAPI::V2_0::UserEntry; VUser::Google::ProvisioningAPI::V2_0::NicknameEntry; VUser::Google::ProvisioningAPI::V2_0::EmailListEntry; and VUser::Google::ProvisioningAPI::V2_0::EmailListRecipientEntry. The official Google documentation can be found at http://code.google.com/apis/apps-for-your-domain/google_apps_provisioning_api_v2.0_reference.html http://code.google.com/apis/apps/gdata_provisioning_api_v2.0_reference.html For support, see the Google Group at http://groups.google.com/group/apps-for-your-domain-apis For additional support specific to this modules, email me at johan at reinalda dot net. =head1 AUTHOR Johan Reinalda, johan at reinalda dot net Randy Smith, perlstalker at vuser dot org =head1 COPYRIGHT AND LICENSE Copyright (C) 2006 by Johan Reinalda, johan at reinalda dot net Copyright (C) 2007 by Randy Smith, perlstalker at vuser dot org This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. If you make useful modification, kindly consider emailing then to me for inclusion in a future version of this module. =cut 1; VUser-Google-Api-1.0.1/lib/VUser/Google/Provisioning/0000755000175000017500000000000011570731200022110 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/Google/Provisioning/V2_0.pm0000644000175000017500000004412011570726510023165 0ustar rbsmithrbsmithpackage VUser::Google::Provisioning::V2_0; use warnings; use strict; our $VERSION = '0.2.0'; use Moose; extends 'VUser::Google::Provisioning'; use VUser::Google::Provisioning::UserEntry; has '+base_url' => (default => 'https://apps-apis.google.com/a/feeds/'); #### Methods #### ## Users # # %options # userName* # givenName* # familyName* # password* # hashFunctioName (SHA-1|MD5) # suspended (bool) # quota (in MB) # changePasswordAtNextLogin (bool) # admin (bool) sub CreateUser { my $self = shift; my %options = (); if (ref $_[0] and $_[0]->isa('VUser::Google::Provisioning::UserEntry')) { %options = $_[0]->as_hash; } else { %options = @_; } $self->google()->Login(); my $url = $self->base_url.$self->google->domain.'/user/2.0'; my $post = ' '; ## login $post .= '_escape_quotes($options{'password'})."\""; if ($options{hashFunctionName}) { $post .= " hashFunctionName=\"$options{hashFunctionName}\""; } if ($options{suspended}) { $post .= ' suspended="'.$self->_as_bool($options{suspended}).'"'; } if ($options{changePasswordAtNextLogin}) { $post .= ' changePasswordAtNextLogin="' .$self->_as_bool($options{changePasswordAtNextLogin}).'"'; } if ($options{admin}) { $post .= ' admin="'.$self->_as_bool($options{admin}).'"'; } $post .= '/>'; ## quota if ($options{quota}) { $post .= ""; } ## name $post .= 'google->Request('POST', $url, $post)) { ## build UserEntry $self->dprint('Created user'); my $entry = $self->_build_user_entry($self->google->result); return $entry; } else { ## ERROR! $self->dprint('CreateUser failed: '.$self->google->result->{reason}); die "Error creating user: ".$self->google->result->{'reason'}."\n"; } } sub RetrieveUser { my $self = shift; my $username = shift; my $url = $self->base_url.$self->google->domain.'/user/2.0/'.$username; if ($self->google->Request('GET', $url)) { return $self->_build_user_entry($self->google->result); } else { if ($self->google->result->{'reason'} =~ /EntityDoesNotExist/) { return undef; } else { die "Error retrieving user: ".$self->google->result->{'reason'}."\n"; } } } # Retrieve one page of users. # How to return the next page? # Returns ( # entries => \@entries, # list of UserEntry objects # next => $next # the next username if another page exists # # undef otherwise # ) sub RetrieveUsers { my $self = shift; my $start_user = shift; my @entries = (); my $next_user; my $url = $self->base_url.$self->google->domain.'/user/2.0'; if ($start_user) { $url .= "?startUsername=$start_user"; } if ($self->google->Request('GET', $url)) { foreach my $entry (@{ $self->google->result->{'entry'} }) { ## Create UserEntry object my $user = $self->_build_user_entry($entry); push @entries, $user; } } else { ## There was an error die "Error fetching users: ".$self->google->result->{'reason'}."\n"; } # Look for the a link tag that says there should be more results # A link tag with rel=next means there is another page foreach my $link (@{ $self->google->result->{'link'} }) { if ($link->{'rel'} eq 'next') { $url = $link->{'href'}; if ($url =~ /startUsername=([^\"]+)/) { $next_user = $1; } } } return ( entries => \@entries, next => $next_user ); } # Alias for RetrieveUsers sub RetrievePageOfUsers { $_[0]->RetrieveUsers(@_); } # Returns a list of UserEntry objects sub RetrieveAllUsers { my $self = shift; my @entries = (); my $next; my %results; eval { %results = $self->RetrieveUsers; push @entries, @{ $results{'entries'} }; $next = $results{'next'}; }; die $@ if $@; while ($next) { eval { %results = $self->RetrieveUsers($next); push @entries, @{ $results{'entries'} }; $next = $results{'next'}; }; die $@ if $@; } return @entries; } # %options # userName* # givenName # familyName # password # hashFunctioName (SHA-1|MD5) # suspended (bool) # quota (in MB) # changePasswordAtNextLogin (bool) # admin (admin) sub UpdateUser { my $self = shift; my %options = (); if (ref $_[0] and $_[0]->isa('VUser::Google::Provisioning::UserEntry')) { %options = $_[0]->as_hash; } else { %options = @_; } die "Can't update user: userName not set\n" unless $options{'userName'}; my $url = $self->base_url.$self->google->domain ."/user/2.0/$options{userName}"; my $post = ' '; ## update user info (login tag) if ($options{password} or defined $options{suspended} or defined $options{changePasswordAtNextLogin} or defined $options{admin} ) { $post .= '_escape_quotes($options{'password'}); $post .= '"'; if (defined $options{hashFunctionName}) { $post .= ' hashFunctionName="'; $post .= $options{hashFunctionName}; $post .= '"'; } } if (defined $options{suspended}) { $post .= ' suspended="'.$self->_as_bool($options{suspended}).'"'; } if (defined $options{changePasswordAtNextLogin}) { $post .= ' changePasswordAtNextLogin="' .$self->_as_bool($options{changePasswordAtNextLogin}).'"'; } if (defined $options{admin}) { $post .= ' admin="'.$self->_as_bool($options{admin}).'"'; } $post .= '/>'; } ## Quota if ($options{quota}) { $post .= ""; } ## Name if ($options{givenName} or $options{familyName}) { $post .= 'google->Request('PUT', $url, $post)) { $self->dprint('Updated user'); my $entry = $self->_build_user_entry($self->google->result); return $entry; } else { die "Error updating user: ".$self->google->result->{'reason'}."\n"; } } sub RenameUser { my $self = shift; my $oldname = shift; my $newname = shift; die "Can't rename user: old userName not set\n" unless $oldname; die "Can't rename user: new userName not set\n" unless $newname; my $url = $self->base_url.$self->google->domain ."/user/2.0/$oldname"; my $user = $self->RetrieveUser($oldname) or die "Unknown user: $oldname\n"; my $post = ' '; $post .= '$oldname'; $post .= 'base_url. $self->google->domain."/user/2.0/$oldname\"/>"; $post .= 'base_url. $self->google->domain."/user/2.0/$oldname\"/>"; $post .= "_as_bool($user->Suspended).'"'; $post .= ' admin="'.$self->_as_bool($user->Admin).'"'; $post .= ' changePasswordAtNextLogin="' .$self->_as_bool($user->ChangePasswordAtNextLogin).'"'; # $post .= ' agreedToTerms="'.$self->_as_bool($user->AgreedToTerms).'"'; $post .= "/>"; $post .= ''; if ($self->google->Request('PUT', $url, $post)) { $self->dprint("Renamed $oldname to $newname"); my $entry = $self->_build_user_entry($self->google->result); return $entry; } else { die "Error rename user: ".$self->google->result->{'reason'}."\n"; } } sub DeleteUser { my $self = shift; my $user; if (ref $_[0] and $_[0]->isa('VUser::Google::Provisioning::UserEntry')) { $user = $_[0]->UserName } else { $user = $_[0]; } my $url = $self->base_url.$self->google->domain.'/user/2.0/'.$user; if ($self->google->Request('DELETE', $url)) { return 1; } else { return undef; } } sub ChangePassword { my $self = shift; my $username = shift; my $password = shift; my $hash_function = shift; if (not $username or not $password) { die "Can't change password: username or password not set.\n"; } my $entry = $self->UpdateUser( userName => $username, password => $password, hashFunctionName => $hash_function, ); return $entry; } ## Nicknames sub CreateNickname { } sub RetrieveNickname { } sub RetrieveAllNicknamesForUser { } sub RetrieveAllNicknamesInDomain { } sub DeleteNickname { } # Takes the parsed XML object sub _build_user_entry { my $self = shift; my $xml = shift; my $entry = VUser::Google::Provisioning::UserEntry->new(); $entry->UserName($xml->{'apps:login'}[0]{'userName'}); if ($xml->{'apps:login'}[0]{'suspended'}) { if ($xml->{'apps:login'}[0]{'suspended'} eq 'true') { $entry->Suspended(1); } else { $entry->Suspended(0); } } if ($xml->{'apps:login'}[0]{'changePasswordAtNextLogin'}) { if ($xml->{'apps:login'}[0]{'changePasswordAtNextLogin'} eq 'true') { $entry->ChangePasswordAtNextLogin(1); } else { $entry->ChangePasswordAtNextLogin(0); } } if ($xml->{'apps:login'}[0]{'admin'}) { if ($xml->{'apps:login'}[0]{'admin'} eq 'true') { $entry->Admin(1); } else { $entry->Admin(0); } } if ($xml->{'apps:login'}[0]{'agreedToTerms'}) { if ($xml->{'apps:login'}[0]{'agreedToTerms'} eq 'true') { $entry->AgreedToTerms(1); } else { $entry->AgreedToTerms(0); } } $entry->FamilyName($xml->{'apps:name'}[0]{'familyName'}); $entry->GivenName($xml->{'apps:name'}[0]{'givenName'}); $entry->Quota($xml->{'apps:quota'}[0]{'limit'}); return $entry; } no Moose; __PACKAGE__->meta->make_immutable; 1; __END__ =head1 NAME VUser::Google::Provisioning::V2_0 - Support for version 2.0 of the Google Provisioning API =head1 SYNOPSIS use VUser::Google::ApiProtocol::V2_0; use VUser::Google::Provisioning::V2_0; my $google = VUser::Google::ApiProtocol::V2_0->new( domain => 'example.com', admin => 'admin_user', password => 'secret', ); my $api = VUser::Google::Provisioning::V2_0->new( google => $google, ); ## Create user my $new_user = $api->CreateUser( userName => 'fflintstone', givenName => 'Fred', familyName => 'Flintstone', password => 'I<3Wilma', ); ## Retrieve a user my $user = $api->RetrieveUser('fflintstone'); ## Retrieve all userr my @users = $api->RetrieveAllUsers(); ## Update a user my $updated = $api->UpdateUser( userName => 'fflintstone', givenName => 'Fredrock', familyName => 'FlintStone', suspended => 1, quota => 2048, ); ## Change password $updated = $api->ChangePassword('fflintstone', 'new-pass'); $updated = $api->ChangePassword( 'fflintstone', '51eea05d46317fadd5cad6787a8f562be90b4446', 'SHA-1', ); $updated = $api->ChangePassword( 'fflintstone', 'd27117a019717502efe307d110f5eb3d', 'MD5', ); ## Delete a user my $rc = $api->DeleteUser('fflintstone'); =head1 DESCRIPTION VUser::Google::Provisioning::V2_0 provides support for managing users using version 2.0 of the Google Provisioning API. In order to use the Google Provisioning API, you must turn on API support from the Google Apps for Your Domain control panel. The user that is used to create the VUser::Google::ApiProtocol object must have administrative privileges on the domain. B It's a good idea to log into the web control panel at least once as the API user in order to accept the the terms of service and admin terms. If you don't, you'll get intermittent authentication errors when trying to use the API. =head2 METHODS Unless stated otherwise, these methods will die() if there is an API error. =head3 CreateUser CreateUser() takes a hash of create options and returns a VUser::Google::Provisioning::UserEntry object if the account was created. CreateUser() will die() if there is an error. The keys of the hash are: =over =item userName (required) The user name of the account to create =item givenName (required) The user's given name =item familyName (required) The user's family name =item password (required) The user's password. If hashFunctionName is also set, this is the base16-encoded hash of the password. Otherwise, this is the user's plaintext password. Google required that passwords be, at least, six characters. =item hashFunctionName hashFunctionName must be I or I. If this is set, password is the base16-encoded password hash. =item quota The user's quota in MB. Not all domains will be allowed to set users' quotas. If that's the case, creation will still succeed but the quota will be set to the default for your domain. =item changePasswordAtNextLogin If set to a true value, e.g. C<1>, the user will be required to change their password the next time they login in. This is the default. You may turn this off by setting changePasswordAtNextLogin to C<0>. =item admin If set to a true value, e.g. C<1>, the user will be granted administrative privileges. A false value, e.g. C<0>, admin rights will be revoked. By default, users will not be granted admin rights. =back =head3 RetrieveUser my $user = $api->RetrieveUser('fflintstone'); Retrieves a specified user by the user name. RetieveUser will return a VUser::Google::Provisioning::UserEntry if the user exists and undef if it doesn't. =head3 RetrieveUsers my @users = (); my %results = $api->RetrieveUsers(); @users = @{ $results{entries} }; while ($results{next}) { %results = $api->RetrieveUsers($results{next}); push @users, @{ $results{entries} }; } Fetches one page of users starting at a given user name. Currently, a page is defined as 100 users. This is useful if you plan on paginating the results yourself or if you have a very large number of users. The returned result is a hash with the following keys: =over =item entries A list reference containing the user accounts. Each entry is a VUser::Google::Provisioning::UserEntry object. =item next The user name for the start of the next page. This will be undefined (C) if there are no more pages. =back See RetrieveAllUsers() if you want to fetch all of the accounts at once. =head3 RetrievePageOfUsers This is a synonym for RetrieveUsers() =head3 RetrieveAllUsers my @users = $api->RetrieveAllUsers(); Get a list of all the users for the domain. The entries in the list are VUser::Google::Provisioning::UserEntry objects. =head3 UpdateUser my $updated = $api->UpdateUser( userName => 'fflintstone', givenName => 'Fred', # ... other options ); Updates an account. UpdateUser takes the same options as CreateUser() but only userName is required. UpdateUser() cannot be used to rename an account. See RenameUser(). =head3 RenameUser my $user_user = $api->RenameUser($oldname, $newname); Rename an account. The first parameter is the old user name; the second is the new user name. RenameUser() will die if the old name does not exist. =head3 DeleteUser my $rc = $api->DeleteUser('fflintstone'); Deletes a given user. Returns true if the delete succeded and dies if there was an error. =head3 ChangePassword $updated = $api->ChangePassword('fflintstone', 'new-pass'); $updated = $api->ChangePassword( 'fflintstone', '51eea05d46317fadd5cad6787a8f562be90b4446', 'SHA-1', ); $updated = $api->ChangePassword( 'fflintstone', 'd27117a019717502efe307d110f5eb3d', 'MD5', ); Change a users password. ChangePassword takes the user name, password and, optionally, a hash function name. If the hash function name is set, the password, is the base16-encoded password, otherwise it is the clear text password. Accepted values for the has function name are I and I. There is no difference between using this and using UpdateUser to change the user's password. =head1 SEE ALSO =over =item * VUser::Google::Provisioning =item * VUser::Google::ApiProtocol::V2_0 =item * VUser::Google::EmailSettings::V2_0 =item * http://code.google.com/apis/apps/gdata_provisioning_api_v2.0_developers_protocol.html item * http://code.google.com/apis/apps/gdata_provisioning_api_v2.0_reference.html =back =head1 BUGS Bugs may be reported at http://code.google.com/p/vuser/issues/list. =head1 AUTHOR Randy Smith =head1 COPYRIGHT AND LICENSE Copyright (C) 2009 Randall Smith This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =cut VUser-Google-Api-1.0.1/lib/VUser/Google/Provisioning/UserEntry.pm0000644000175000017500000000214511570726510024420 0ustar rbsmithrbsmithpackage VUser::Google::Provisioning::UserEntry; use warnings; use strict; our $VERSION = '0.2.0'; use Moose; has 'UserName' => (is => 'rw', isa => 'Str'); has 'GivenName' => (is => 'rw', isa => 'Str'); has 'FamilyName' => (is => 'rw', isa => 'Str'); has 'Password' => (is => 'rw', isa => 'Str'); has 'HashFunctionName' => (is => 'rw', isa => 'Str'); has 'Suspended' => (is => 'rw', isa => 'Bool', default => 0); has 'Quota' => (is => 'rw', isa => 'Int'); has 'ChangePasswordAtNextLogin' => (is => 'rw', isa => 'Bool', default => 0); has 'Admin' => (is => 'rw', isa => 'Bool', default => 0); has 'AgreedToTerms' => (is => 'rw', isa => 'Bool', default => 0); sub as_hash { my $self = shift; my %hash = ( userName => $self->UserName, givenName => $self->GivenName, familyName => $self->FamilyName, password => $self->Password, hashFunctionName => $self->HashFunctionName, suspended => $self->Suspended, quota => $self->Quota, changePasswordAtNextLogin => $self->ChangePasswordAtNextLogin, admin => $self->Admin, ); return %hash; } no Moose; __PACKAGE__->meta->make_immutable; 1; VUser-Google-Api-1.0.1/lib/VUser/Google/EmailSettings/0000755000175000017500000000000011570731200022172 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/Google/EmailSettings/V2_0.pm0000644000175000017500000002447211570726510023257 0ustar rbsmithrbsmithpackage VUser::Google::EmailSettings::V2_0; use warnings; use strict; # Copyright (C) 2009 Randy Smith, perlstalker at vuser dot org our $VERSION = '0.1.0'; use Moose; extends 'VUser::Google::EmailSettings'; # BUG: This should work but doesn't seem to. WTF? #has '+google' => (isa => 'VUser::Google::ApiProtocol::V2_0'); has '+base_url' => (default => 'https://apps-apis.google.com/a/feeds/emailsettings/2.0/'); ## Methods # Constructor sub BUILD {} override 'CreateLabel' => sub { my $self = shift; my %options = @_; my $label = $options{'label'}; $self->google()->Login(); my $url = $self->base_url().$self->google()->domain().'/'.$self->user().'/label'; my $post = " "; return $self->google->Request('POST', $url, $post); }; override 'CreateFilter' => sub { my $self = shift; my %options = @_; $self->google()->Login(); my $url = $self->base_url().$self->google->domain().'/'.$self->user().'/filter'; my $post = ' '; ## Add criteria if (defined $options{hasAttachment}) { $options{hasAttachment} = $options{hasAttachment}? 'true':'false'; } foreach my $crit qw(from to subject hasTheWord doesNotHaveTheWord hasAttachment) { if (defined $options{$crit}) { $post .= sprintf ("", $crit, $options{$crit}); } } ## Add actions foreach my $act qw(shouldMarkAsRead shouldArchive) { $options{$act} = $options{$act}? 'true':'false'; } foreach my $act qw(label shouldMarkAsRead shouldArchive) { if (defined $options{$act}) { $post .= sprintf ("", $act, $options{$act}); } } $post .= ''; return $self->google->Request('POST', $url, $post); }; override 'CreateSendAsAlias' => sub { my $self = shift; my %options = @_; my $name = $options{'name'}; my $address = $options{'address'}; my $reply_to = $options{'replyTo'}; my $make_default = $options{'makeDefault'}; $self->google()->Login(); my $url = $self->base_url().$self->google->domain().'/'.$self->user().'/sendas'; my $post = ' '; $post .= ""; $post .= ""; if (defined $reply_to) { $post .= ""; } if (defined $make_default) { $post .= sprintf("", $make_default? 'true' : 'false' ); } $post .= ''; return $self->google->Request('POST', $url, $post); }; override 'UpdateWebClip' => sub { my $self = shift; my %options = @_; my $enable = $options{'enable'}; $self->google()->Login(); my $url = $self->base_url().$self->google->domain().'/'.$self->user().'/webclip'; my $post = ''; $post .= ''; $post .= sprintf('', $enable ? 'true' : 'false' ); $post .= ''; return $self->google->Request('PUT', $url, $post); }; override 'UpdateForwarding' => sub { my $self = shift; my %options = @_; my $enable = $options{'enable'}; my $forward_to = $options{'forwardTo'}; my $action = $options{'action'}; $action = uc($action); $self->google()->Login(); my $url = $self->base_url().$self->google->domain().'/'.$self->user().'/forwarding'; my $post = ''; $post .= ''; if (defined $enable) { $post .= sprintf('', $enable ? 'true' : 'false'); } if ($enable) { if ($forward_to) { $post .= ""; } if ($action) { if ($action ne 'KEEP' and $action ne 'ARCHIVE' and $action ne 'DELETE' ) { die "action must be KEEP, ARCHIVE or DELETE"; } $post .= ""; } } $post .= ''; return $self->google->Request('PUT', $url, $post); }; override 'UpdatePOP' => sub { my $self = shift; my %options = @_; my $enable = $options{'enable'}; my $enable_for = $options{'enableFor'}; my $action = $options{'action'}; $action = uc($action); $self->google()->Login(); my $url = $self->base_url().$self->google->domain().'/'.$self->user().'/pop'; my $post = ''; $post .= ''; if (defined $enable) { $post .= sprintf('', $enable ? 'true' : 'false'); } if ($enable) { if ($enable_for) { $post .= ""; } if ($action) { if ($action ne 'KEEP' and $action ne 'ARCHIVE' and $action ne 'DELETE' ) { die "action must be KEEP, ARCHIVE or DELETE"; } $post .= ""; } } $post .= ''; return $self->google->Request('PUT', $url, $post); }; override 'UpdateIMAP' => sub { my $self = shift; my %options = shift; my $enable = $options{'enable'}; $self->google()->Login(); my $url = $self->base_url().$self->google->domain().'/'.$self->user().'/imap'; my $post = ''; $post .= ''; if (defined $enable) { $post .= sprintf('', $enable ? 'true' : 'false'); } $post .= ''; return $self->google->Request('PUT', $url, $post); }; override 'UpdateVacationResponder' => sub { my $self = shift; my %options = @_; my $enable = $options{'enable'}; my $subject = $options{'subject'}; my $message = $options{'message'}; my $contacts = $options{'contactsOnly'}; $self->google->Login(); my $url = $self->base_url().$self->google->domain.'/'.$self->user.'/vacation'; my $post = ''; $post .= ''; $post .= sprintf('', $enable ? 'true' : 'false'); if (defined $enable) { $post .= sprintf('', defined $subject ? $subject : ''); $post .= sprintf('', defined $message ? $message : ''); $post .= sprintf('', $contacts ? 'true' : 'false'); } $post .= ''; return $self->google->Request('PUT', $url, $post); }; override 'UpdateSignature' => sub { my $self = shift; my %options = shift; my $sig = $options{'signature'}; $self->google->Login(); my $url = $self->base_url().$self->google->domain.'/'.$self->user.'/signature'; my $post = ''; $post .= ''; $post .= sprintf('', $sig ? $sig : ''); $post .= ''; return $self->google->Request('PUT', $url, $post); }; override 'UpdateLanguage' => sub { my $self = shift; my %options = shift; my $lang = $options{'language'}; $self->google->Login(); my $url = $self->base_url().$self->google->domain.'/'.$self->user.'/language'; if ($lang !~ /^\w\w(?:-\w\w)?/i) { $lang = 'en-US'; } my $post = ''; $post .= ''; $post .= sprintf('', $lang ? $lang : ''); $post .= ''; return $self->google->Request('PUT', $url, $post); }; override 'UpdateGeneral' => sub { my $self = shift; my %options = @_; $self->google->Login(); my $url = $self->base_url().$self->google->domain.'/'.$self->user.'/general'; foreach my $opt qw(shortcuts arrows snippets unicode) { $options{$opt} = $options{$opt}? 'true':'false'; } my $post = ''; $post .= ''; foreach my $opt (keys %options) { if (defined $options{$opt}) { $post .= sprintf ("", $opt, $options{$opt}); } } $post .= ''; return $self->google->Request('PUT', $url, $post); }; no Moose; 1; __END__ =head1 NAME VUser::Google::EmailSettings::V2_0 - Support version 2.0 of the Google Email Settings API =head1 SEE ALSO L, L, L =over 4 =item Google Email Settings API http://code.google.com/apis/apps/email_settings/developers_guide_protocol.html =back =head1 BUGS Report bugs at http://code.google.com/p/vuser/issues/list. =head1 AUTHOR Randy Smith, perlstalker at vuser dot net =head1 COPYRIGHT AND LICENSE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. If you make useful modification, kindly consider emailing then to me for inclusion in a future version of this module. =cut VUser-Google-Api-1.0.1/lib/VUser/Google/Provisioning.pm0000644000175000017500000000157511570726511022467 0ustar rbsmithrbsmithpackage VUser::Google::Provisioning; use warnings; use strict; our $VERSION = '0.2.0'; use Moose; has 'google' => ( is => 'rw', isa => 'VUser::Google::ApiProtocol', required => 1 ); has 'base_url' => (is => 'rw', isa => 'Str'); # Turn on deugging has 'debug' => (is => 'rw', default => 0); #### Methods ## Util #print out debugging to STDERR if debug is set sub dprint { my $self = shift; my $text = shift; my @args = @_; if( $self->debug and defined ($text) ) { print STDERR sprintf ("$text\n", @args); } } # Escape " with " for XML sub _escape_quotes { my $self = shift; my $text = shift; $text =~ s/\"/"/; return $text; } # Replace 1 with 'true' other with 0 sub _as_bool { my $self = shift; my $value = shift; return $value ? 'true' : 'false'; } no Moose; __PACKAGE__->meta->make_immutable; 1; __END__ VUser-Google-Api-1.0.1/lib/VUser/Google/Groups.pm0000644000175000017500000000114711570726511021253 0ustar rbsmithrbsmithpackage VUser::Google::Groups; use warnings; use strict; our $VERSION = '0.2.0'; use Moose; has 'google' => ( is => 'rw', isa => 'VUser::Google::ApiProtocol', required => 1 ); has base_url => (is => 'rw', isa => 'Str'); # Turn on deugging has 'debug' => (is => 'rw', default => 0); #### Methods #### ## Util #print out debugging to STDERR if debug is set sub dprint { my $self = shift; my $text = shift; my @args = @_; if( $self->debug and defined ($text) ) { print STDERR sprintf ("$text\n", @args); } } no Moose; __PACKAGE__->meta->make_immutable; 1; __END__ VUser-Google-Api-1.0.1/lib/VUser/Google/ApiProtocol/0000755000175000017500000000000011570731200021655 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/Google/ApiProtocol/V2_0.pm0000644000175000017500000002326711570726511022744 0ustar rbsmithrbsmithpackage VUser::Google::ApiProtocol::V2_0; use warnings; use strict; use XML::Simple; use LWP::UserAgent qw(:strict); use HTTP::Request qw(:strict); use Encode; use Carp; use Data::Dumper; use Moose; extends 'VUser::Google::ApiProtocol'; our $VERSION = '0.5.1'; has 'google_host' => (is => 'ro', default => 'www.google.com' ); has 'google_token_url' => (is => 'ro', default => 'https://www.google.com/accounts/ClientLogin' ); has 'max_token_age' => (is => 'ro', default => 86400 ); # base url to the Google REST API has 'google_baseurl' => (is => 'ro', default => 'https://www.google.com/a/feeds/' ); has 'google_apps_schema' => (is => 'ro', default => 'http://schemas.google.com/apps/2006' ); has 'success_code' => (is => 'ro', default => 'Success(2000)' ); has 'failure_code' => (is => 'ro', default => 'Failure(2001)' ); has 'max_name_length' => (is => 'ro', default => '40'); has 'max_username_length' => (is => 'ro', default => '30'); override 'Login' => sub { my $self = shift; #print STDERR "LOGIN: debug=".$self->debug."\n"; $self->dprint("Relogin called"); return 1 if $self->IsAuthenticated() and not $self->refresh_token(); my $retval = 0; my $stats = $self->stats(); $stats->{logins}++; ## Clear last results $self->_set_reply_headers(''); $self->_set_reply_content(''); $self->_set_result({}); ## Create an LWP object to make the HTTP POST request my $lwp = LWP::UserAgent->new; if (defined $lwp) { $lwp->agent($self->useragent); $lwp->from($self->admin.'@'.$self->domain); # Submit the request with values for # accountType, Email and Passwd variables my $response = $lwp->post($self->google_token_url, ['accountType' => 'HOSTED', 'Email' => $self->admin.'@'.$self->domain, 'Passwd' => $self->password, 'service' => 'apps' ] ); # save the reply page $self->_set_reply_headers($response->headers->as_string); $self->_set_reply_content($response->content); if ($response->is_success) { # Extract the authentication token from the response foreach my $line (split(/\n/, $response->content)) { $self->dprint("RECV'd: $line"); if ($line =~ m/^Auth=(.+)$/) { $self->_set_authtoken($1); $self->_set_authtime(time()); $self->dprint("Token found: %s", $self->authtoken); # Clear refresh $self->refresh_token(0); $retval = 1; last; } } } else { $self->dprint("Error in login: %s", $response->status_line); $self->_set_result({reason => "Error in login: ".$response->status_line}); } } else { $self->dprint("Error getting LWP object: $!"); $self->_set_result({reason => "Error getting LWP object: $!"}); } $self->_set_stats($stats); return $retval; }; override 'IsAuthenticated' => sub { #get object reference my $self = shift(); my $token_age = time - $self->authtime(); if( $self->refresh_token() or ( $token_age > $self->max_token_age() ) ) { $self->dprint("Refresh token: %s; time-auth: %d; max_age: %d", $self->refresh_token, $token_age, $self->max_token_age); return 0; } #we are still okay! return 1; }; override 'Request' => sub { my $self = shift; my $retval = 0; $self->dprint( "*** REQUEST ***" ); # relogin if needed $self->Login; # clear last results $self->_set_reply_headers(''); $self->_set_reply_content(''); $self->_set_result({}); if (@_ != 2 and @_ != 3) { $self->_set_result({reason => 'Invalid number of arguments to Request()'}); return 0; } # get paramters my ($method, $url, $body) = @_; $self->dprint("Method: $method; URL: $url"); $self->dprint("Body: $body") if $body; ## Keep some stats my $stats = $self->stats; $stats->{requests}++; $stats->{rtime} = time; ## Check if we are authenticated to google if (not $self->IsAuthenticated()) { $self->dprint("Error autheticating"); $self->_set_stats($stats); return 0; } ## Properly encode the body $body = encode('UTF-8', $body); ## Create an LWP object to make the HTTP POST request my $ua = LWP::UserAgent->new; if (not defined $ua) { $self->dprint("Cannot create LWP::UserAgent: $!"); $self->_set_result({reason => "Cannotcreate LWP::UserAgent in Request: $!"}); $self->_set_stats($stats); return 0; } #and create the request object where are we connecting to # v2.0 uses a diffent url based what's being done. # The API methods will construct the URL becuase action specific # information, such as domain and user, is embedded with it. # v2.0 use different methods depending on the action # It's up to the API methods to know which method to use my $req = HTTP::Request->new($method => $url); if (not defined $req) { $self->dprint("Cannot create HTTP::Request object: $!"); $self->_set_result({reason => "Cannot create HTTP::Request object in Request(): $!"}); $self->_set_stats($stats); return $retval; } # Set some user agent variables $ua->agent($self->useragent); $ua->from('<'.$self->admin.'@'.$self->domain.'>'); # Submit the request $req->header('Accept' => 'application/atom+xml'); $req->header('Content-Type' => 'application/atom+xml'); if ($body) { $req->header('Content-Length' => length($body) ); } $req->header('Connection' => 'Keep-Alive'); $req->header('Host' => $self->google_host); $req->header('Authorization' => 'GoogleLogin auth='.$self->authtoken); # Assign the data to the request # Perhaps if $method eq 'GET' or 'DELETE' would be better if ($body) { $req->content($body); } ## Execute the request my $response = $ua->request($req); $self->dprint(Data::Dumper::Dumper($response)); # Save reply page $self->_set_reply_headers($response->headers->as_string); $self->_set_reply_content($response->content); # Check result if ($response->is_success) { $stats->{success}++; $self->dprint("Success in post:"); my $xml = decode('UTF-8', $response->content); $self->dprint($xml); if ($xml) { ## Parse the XML using XML::Simple my $simple = XML::Simple->new(ForceArray => 1); $self->_set_result($simple->XMLin($xml)); $self->dprint(Dumper($self->{result})); } else { $self->_set_result({}); } $self->dprint("Google API success!"); $retval = 1; } else { # OK. Funky issue. When trying to get a user that doesn't exist, # Google throws a 400 error instead of returning a error document. # Google has fun. If there is a problem with the request, # google triggers a 400 error which then fails on ->is_success. # So, we need to check the content anyway to see if there is a # reason for the failure. $self->dprint("Google API failure!"); my $xml = decode('UTF-8', $response->content); $self->dprint($xml); if ($xml) { my $simple = XML::Simple->new(ForceArray => 1); $self->_set_result($simple->XMLin($xml)); $self->dprint('Error result: %s', Dumper($self->result)); } if (defined ($self->result()->{error}[0]{reason})) { my $error = sprintf("Google API failure: %s - %s", $self->result()->{error}[0]{errorCode}, $self->result()->{error}[0]{reason} ); $self->dprint($error); my $res = $self->result; $res->{reason} = $error; $self->_set_result($res); } else { $self->dprint("Error in post: %s", $response->status_line); my $res = $self->result; $res->{reason} = "Error in post: ".$response->status_line; $self->_set_result($res); } } return $retval; }; no Moose; __PACKAGE__->meta->make_immutable; 1; __END__ =head1 NAME VUser::Google::ApiProtocol::V2_0 - Implements version 2.0 of the Google APIs. =head1 SYSNOPSIS use VUser::Google::ApiProtocol::V2_0; ## Create a new connection my $google = VUser::Google::ApiProtocol::V2_0->new( domain => 'your.google-apps-domain.com', admin => 'admin_user', password => 'admin_user password', ); ## Login to the Google Apps API # Login() uses the credentials provided in new() $google->Login(); ## Create a new request # Create the URL to send to API request to. # See the API docs for the valid URLs my $url = "https://apps-apis.google.com/a/feeds/emailsettings/2.0/" $url .= "your.google-apps-domain.com/username/label"; # Create XML message to send to Google # See the API docs for the valid XML to send my $xml = '...'; # NB: The method (POST here) may be different depending on API call my $success = $google->Request('POST', $url, $xml); # Get the parsed response if ($success) { # $result is the XML reply parsed by XML::Simple my $result = $google->get_result; } else { # $result is the error message from google # parsed by XML::Simple with the addition of a # 'reason' key which contains the error. my $result = $google->get_result; die "Error: $result->{reason}"; } =head1 DESCRIPTION Implements version 2.0 of the Google API. See L for a list of members and methods. =head1 SEE ALSO L, L =head1 AUTHOR Randy Smith Adapted from code from Johan Reinalda =head1 LICENSE Copyright (C) 2006 by Johan Reinalda, johan at reinalda dot net Copyright (C) 2009 by Randy Smith, perlstalker at vuser dot org This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. If you make useful modification, kindly consider emailing then to me for inclusion in a future version of this module. =cut VUser-Google-Api-1.0.1/lib/VUser/Google/ApiProtocol.pm0000644000175000017500000001473311570730671022236 0ustar rbsmithrbsmithpackage VUser::Google::ApiProtocol; use warnings; use strict; use Moose; our $VERSION = '1.0.1'; ## Members # The Google hosted domain we are accessing has 'domain' => (is => 'rw'); # The admin account has 'admin' => (is => 'rw'); # Admin password has 'password' => (is => 'rw'); # Turn on deugging has 'debug' => (is => 'rw', default => 0); # If set, will force re-authentication has 'refresh_token' => (is => 'rw', isa => 'Bool', default => 0, #init_arg => undef ); # The authentication token returned from Google has 'authtoken' => (is => 'rw', writer => '_set_authtoken', #init_arg => undef ); # Time when auth happened; only valid for 24 hours # Unix timestamp has 'authtime' => (is => 'rw', default => 0, writer => '_set_authtime', #init_arg => undef ); # the last http content posted from Google has 'request_content' => (is => 'rw', writer => '_set_request_content', #init_arg => undef ); # The http headers of the last reply has 'reply_headers' => (is => 'rw', writer => '_set_reply_headers', #init_arg => undef ); # The http content of the last reply has 'reply_content' => (is => 'rw', writer => '_set_reply_content', #init_arg => undef ); # The resulting hash from the last reply data as parsed # by XML::Simple has 'result' => (is => 'rw', isa => 'HashRef', writer => '_set_result', #init_arg => undef ); # Some API statistics has 'stats' => (is => 'rw', isa => 'HashRef', default => sub { {ctime => time(), # object creation time rtime => 0, # time of last request requests => 0, # number of API requests made success => 0, # number of successes logins => 0 # number of authentications }; }, writer => '_set_stats', #init_arg => undef ); has 'useragent' => (is => 'ro', lazy => 1, builder => '_build_useragent' ); has 'version' => (is => 'ro', builder => '_build_version' ); ## Methods sub _build_useragent { my $self = shift; return ref($self).'/'.$self->version(); } sub _build_version { my $self = shift; my $class = ref($self); my $ver; no strict 'refs'; # There has got to be cleaner way to do this. $ver = eval { ${ $class."::VERSION" } }; $ver = $VERSION if $@; return $ver; } sub Login {} sub IsAuthenticated {} #generic request routine that handles most functionality #requires 3 arguments: Method, URL, Body #Method is the HTTP method to use. ('GET', 'POST', etc) #URL is the API URL to talk to. #Body is the xml specific to the action. # This is not used on 'GET' or 'DELETE' requests. sub Request {} #print out debugging to STDERR if debug is set sub dprint { my $self = shift; my $text = shift; my @args = @_; if( $self->debug and defined ($text) ) { if (@_) { print STDERR sprintf ("$text\n", @args); } else { print STDERR "$text\n"; } } } no Moose; __PACKAGE__->meta->make_immutable; 1; __END__ =head1 NAME VUser::Google::ApiProtocol - Base class for implementation of the Google APIs =head1 SYNOPSIS This class is not meant to be used directly. Instead use L. =head1 DESCRIPTION =head1 MEMBERS These are the members of the ApiProtocol class. You get and set the values by using the method of the same name. For example: # Get the domain from the ApiProtocol object my $domain = $google->domain; # Set the domain $google->domain('myappsdomain.com'); Most of the member can be set when the object is created with C. my $google = VUser::ApiProtocol->new( domain => 'myappsdomain.com' ); B VUser::Google::ApiProtocol is not meant to be used directly. Please see the version specific subclasses, such as L, to create a usable object. =head2 Read-write Members =over =item admin The administrative user. This user must have be set as an admin in the Google Apps control panel. Also, be sure to log into the Google Apps control panel once with this user to accept all of the legal garbage or you will see intermittent auth errors. =item debug Turn on debugging output. =item domain The Google Apps domain to work on. =item password The plain text password of the admin user. =item refresh_token If set to a true value, C will refresh the authentication token even if it's not necessary. =back =head2 Read-only members =over =item authtime The unix timestamp of the last authentication. =item authtoken The authentication token retrieved from Google on a successful login. The token is only valid for 24 hours. =item reply_headers The HTTP headers of the last reply =item reply_content The HTTP content of the last reply =item result The resulting hash from the last reply data as parsed by XML::Simple =item useragent The user agent VUser::Google::ApiProtocol uses when talking to Google. It is set to the I. For example, I. =back =head1 METHODS =head2 new (%defaults) Create a new ApiProtocol object. Any read-write member may be set in the call to C. =head2 Login Login to the Google API. C takes no parameters. Instead, you must set the C, C, and C members, then call C. C will use the existing authentication token if it exists and hasn't yet timed out. You may force it to do a full re-authentication by setting C to a true value before calling C. =head2 IsAuthenticated Returns true if the B thinks that it has already authenticated and the token hasn't timed out and a false value otherwise. B C only knows if there's an authtoken and if it's still fresh. It may be possible for Google to decide that a token is not valid which C cannot check. =head2 Request ($method, $url[, $body]) Sends an API request to Google. C<$method> is the HTTP method to use, e.g. I, I, etc. B Many of the API calls use different methods. Double check the API docs to make sure you are using the correct method. C<$url> is the url to use to make the API call. The URLs are defined in the API docs. C<$body> is the XML specific action. Again, see the API docs for the specific format for each API call. C<$body> is not needed when the method is I or I. =head2 dprint ($message) Prints C<$message> to STDERR if C is set to a true value. =head1 SEE ALSO L =head1 AUTHOR Randy Smith =head1 LICENSE VUser-Google-Api-1.0.1/lib/VUser/Google/EmailSettings.pm0000644000175000017500000002160111570726511022541 0ustar rbsmithrbsmithpackage VUser::Google::EmailSettings; use warnings; use strict; # Copyright (C) 2009 Randy Smith, perlstalker at vuser dot org our $VERSION = '0.1.0'; use Moose; ## Members # Provisioning API has 'user' => (is => 'rw', required => 1, isa => 'Str' ); has 'google' => (is => 'rw', isa => 'VUser::Google::ApiProtocol', required => 1 ); has 'base_url' => (is => 'rw', isa => 'Str'); # Turn on deugging has 'debug' => (is => 'rw', default => 0); ## Methods sub CreateLabel { } sub CreateFilter { } sub CreateSendAsAlias { } sub UpdateWebClip { } sub UpdateForwarding { } sub UpdatePOP { } sub UpdateIMAP { } sub UpdateVacationResponder { } sub UpdateSignature { } sub UpdateLanguage { } sub UpdateGeneral { } ## Util #print out debugging to STDERR if debug is set sub dprint { my $self = shift; my $text = shift; my @args = @_; if( $self->debug and defined ($text) ) { print STDERR sprintf ("$text\n", @args); } } no Moose; # Clean up after the moose. 1; __END__ =head1 NAME VUser::Google::ProvisioningAPI::EmailSettings - Manage user email settings in Google Apps for Your Domain. =head1 SYNOPSIS use VUser::Google::ApiProtocol::V2_0; use VUser::Google::EmailSettings::V2_0; ## Create a new connection my $google = VUser::Google::ApiProtocol::V2_0->new( domain => 'your.google-apps-domain.com', admin => 'admin_user', password => 'admin_user password', ); my $settings = VUser::Google::EmailSettings::V2_0->new( google => $google, user => 'username', ); ## Create a new label $settings->CreateLabel('label' => 'newLabel'); ## Create a new filter $settings->CreateFilter( 'from' => 'sender@example.com', 'label' => 'newLabel', 'shouldArchive' => 1, ); ## Create a new send-as alias $settings->CreateSendAsAlias( 'name' => 'Tech Support', 'address' => 'support@example.com', ); ## Update the user's web clip setting $settings->UpdateWebClip('enable' => 0); # Turn off $settings->UpdateWebClip('enable' => 1); # Turn on ## Update forwarding $settings->UpdateForwarding( 'enable' => 1, 'forwardTo' => 'someoneelse@example.com', 'action' => 'KEEP', ); ## Update POP3 settings $settings->UpdatePOP( 'enable' => 1, 'enableFor' => 'MAIL_FROM_NOW_ON', 'action' => 'KEEP', ); ## Update IMAP settings $settings->UpdateIMAP('enable' => 1); ## Update user's vacation message $settings->UpdateVacationResponder( 'enable' => 1, 'subject' => "I'm not here right now", 'message' => "I've lost my mind and have gone to search for it.", 'contactsOnly' => 1, ); ## Update the user's signature $settings->UpdateSignature( 'signature' => 'Joe Cool 555-5555' ); $settings->UpdateSignature('signature' => ''); # clear sig ## Update the display language $settings->UpdateLanguage('language' => 'en-US'); ## Update the user's general settings $settings->UpdateGeneral('pageSize' => 50); # You can set more than one at a time $settings->UpdateGeneral( 'arrows' => 1, 'shortcuts' => 0, ); =head1 DESCRIPTION This is the base class for the Email Settings API. It is not meant to be used directly. Instead see the sub class for each version of the email settings API. =head1 MEMBERS =head2 Read-write members =over =item base_url The C for the Email settings API calls. For example, I. =item debug Turn on debugging output. =item google A VUser::Google::ApiProtocol object. =item user The user name of user to modify. =back =head1 METHODS All of the calls to the Google API take a hash with the options specified by Google. The keys for the hash and what is expected are listed below. Specific versions my use different keys. In general, the keys will match the names of the attributes in the API docs. See the docs for the API version you are using for any differences. B Values that are "true"/"false" are set using Perl values for true and false, i.e. zero for false and anything else for true. =head2 new (%defaults) Create a new EmailSettings object. Any read-write member may be set in the call to C. =head2 dprint ($message) Prints C<$message> to STDERR if C is set to a true value. =head2 CreateLabel (%options) Create a new label. =over =item label The label to create in Google Mail. =back =head2 CreateFilter Create a new mail filter. =over =item from The email must come from this address in order to be filtered. =item to The email must be sent to this address in order to be filtered. =item subject A string the email must have in its subject line to be filtered. =item hasTheWord A string the email can have anywhere in it's subject or body. =item doesNotHaveTheWord A string that the email cannot have anywhere in its subject or body. =item hasAttachment A boolean representing whether or not the email contains an attachment. =item label The name of the label to apply if a message matches the specified filter criteria. =item shouldMarkAsRead Whether to automatically mark the message as read if it matches the specified filter criteria =item shouldArchive Whether to automatically move the message to "Archived" state if it matches the specified filter criteria. =back =head2 CreateSendAsAlias Create a gmail "Send-as alias." =over =item name The name that will appear in the "From" field for this user. =item address The email address that appears as the origination address for emails sent by this user. =item replyTo I<(Optional)> If set, this address will be included as the reply-to address in emails sent using the alias. =item makeDefault I<(Optional)> If set to true, this alias will be become the new default alias to send-as for this user. =back =head2 UpdateWebClip Update the user's "web clip" setting. =over =item enable Whether to enable showing Web clips. =back =head2 UpdateForwarding Update gmail forwarding settings. =over =item enable Whether to enable forwarding of incoming mail. =item forwardTo The email will be forwarded to this address. =item action What Google Mail should do with its copy of the email after forwarding it on. B "KEEP" (in inbox), "ARCHIVE", or "DELETE" (send to trash) =back =head2 UpdatePOP Update the user's POP3 settings. =over =item enable Whether to enable POP3 access. =item enableFor Whether to enable POP3 for all mail, or mail from now on. B "ALL_MAIL", "MAIL_FROM_NOW_ON" =item action What Google Mail should do with its copy of the email after it is retrieved using POP. B "KEEP" (in inbox), "ARCHIVE", or "DELETE" (send to trash) =back =head2 UpdateIMAP Update the user's IMAP settings. =over =item enable Whether to enable IMAP access. =back =head2 UpdateVactionResponder Update the user's vacation auto-responder. =over =item enable Whether to enable the vacation responder. =item subject The subject line of the vacation responder autoresponse. =item message The message body of the vacation responder autoresponse. =item contactsOnly Whether to only send the autoresponse to known contacts. =back =head2 UpdateSignature Update the user's signature. =over =item signature The signature to be appended to outgoing messages. Set the signature to C<''> (the empty string) to clear the signature. =back =head2 UpdateLanguage Update the display language. =over =item language Google Mail's display language. This should be a language tag defined in RFC 3066. See http://code.google.com/apis/apps/email_settings/developers_guide_protocol.html#GA_email_language_tags for a list of supported languages. =back =head2 UpdateGeneral Update the user's general settings. =over =item pageSize The number of conversations to be shown per page. B 25, 50, 100 =item shortcuts Whether to enable keyboard shortcuts. =item arrows Whether to display arrow-shaped personal indicators next to emails that were sent specifically to the user. =item snippets Whether to display snippets of messages in the inbox and when searching. =item unicode Whether to use UTF-8 (unicode) encoding for all outgoing messages, instead of the default text encoding. =back =head1 SEE ALSO L, L =over 4 =item Google Email Settings API http://code.google.com/apis/apps/email_settings/developers_guide_protocol.html =back =head1 BUGS Report bugs at http://code.google.com/p/vuser/issues/list. =head1 AUTHOR Randy Smith, perlstalker at vuser dot net =head1 COPYRIGHT AND LICENSE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. If you make useful modification, kindly consider emailing then to me for inclusion in a future version of this module. =cut VUser-Google-Api-1.0.1/lib/VUser/Google/Groups/0000755000175000017500000000000011570731200020701 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/lib/VUser/Google/Groups/V2_0.pm0000644000175000017500000001420111570726502021754 0ustar rbsmithrbsmithpackage VUser::Google::Groups::V2_0; use warnings; use strict; our $VERSION = '0.2.0'; use Moose; extends 'VUser::Google::Groups'; use VUser::Google::Groups::GroupEntry; has '+base_url' => (default => 'https://apps-apis.google.com/a/feeds/group/2.0/'); #### Methods #### # %options # groupId* # groupName* # description # emailPermission* (Owner | Member | Domain | Anyone) sub CreateGroup { my $self = shift; my %options = (); if (ref $_[0] and $_[0]->isa('VUser::Google::Groups::GroupEntry')) { %options = $_[0]->as_hash; } else { %options = @_; } my $url = $self->base_url.$self->google->domain; my $post = ' '; $post .= ''; $post .= ''; $post .= ''; $post .= ''; $post .= ''; if ($self->google->Request('POST', $url, $post)) { my $entry = $self->_build_group_entry($self->google->result); return $entry; } else { die "Unable to create group: ".$self->google->result->{'reason'}."\n"; } } # Cannot be used to rename the group # %options # groupId* # newGroupId --- no # groupName* # description # emailPermission* sub UpdateGroup { my $self = shift; my %options = (); if (ref $_[0] and $_[0]->isa('VUser::Google::Groups::GroupEntry')) { %options = $_[0]->as_hash; } else { %options = @_; } my $url = $self->base_url.$self->google->domain."/$options{groupId}"; my $post = ' '; if (0 and $options{newGroupId}) { $post .= ''; } if ($options{groupName}) { $post .= ''; } if ($options{description}) { $post .= ''; }; if ($options{emailPermission}) { $post .= ''; }; $post .= ''; if ($self->google->Request('PUT', $url, $post)) { my $entry = $self->_build_group_entry($self->google->result); return $entry; } else { die "Unable to create group: ".$self->google->result->{'reason'}."\n"; } } sub RetrieveGroup { my $self = shift; my $groupid = shift; my $url = $self->base_url.$self->google->domain."/$groupid"; if ($self->google->Request('GET', $url)) { return $self->_build_group_entry($self->google->result); } else { if ($self->google->result->{'reason'} =~ /EntityDoesNotExist/) { return undef; } else { die "Error retrieving group: ".$self->google->result->{'reason'}."\n"; } } } sub RetrieveAllGroupsInDomain { my $self = shift; return $self->RetrieveAllGroupsForMember(); # my $url = $self->base_url.$self->google->domain; # my @groups = (); # if ($self->google->Request('GET', $url)) { # foreach my $entry_xml (@{ $self->google->result->{'entry'} }) { # my $entry = $self->_build_group_entry($entry_xml); # push @groups, $entry; # } # } # else { # die "Cannot retrieve all groups in domain: ". # $self->google->result->{'reason'}; # } # return @groups; } sub RetrieveAllGroupsForMember { my $self = shift; my $member = shift; my $url = $self->base_url.$self->google->domain; if ($member) { $url = '?member='.$member; } my @groups = (); if ($self->google->Request('GET', $url)) { foreach my $entry_xml (@{ $self->google->result->{'entry'} }) { my $entry = $self->_build_group_entry($entry_xml); push @groups, $entry; } } else { die "Cannot retrieve all groups in domain: ". $self->google->result->{'reason'}; } return @groups; } sub DeleteGroup { my $self = shift; my $groupId = shift; die "Cannot delete group: No group specified.\n" if not $groupId; my $url = $self->base_url.$self->google->domain."/$groupId"; if ($self->google->Request('DELETE', $url)) { return 1; } else { die "Cannot delete group ($groupId): ".$self->google->result->{'reason'}; } } sub AddMemberToGroup { my $self = shift; my %options = @_; die "Cannot add member to group: No member specified\n" if not $options{'member'}; die "Cannot add member to group: No group specified\n" if not $options{'group'}; my $url = $self->base_url.$self->google->domain .'/'.$options{group}.'/member'; my $post = ' '; $post .= ""; $post .= ''; if ($self->google->Request('POST', $url, $post)) { return 1; } else { die "Cannot add member to group: ".$self->google->result->{'reason'}."\n"; } } sub RetrieveAllMembersOfGroup { } sub RetrieveMemberOfGroup { } sub RemoveMemberOfGroup { } sub AddOwnerToGroup { } sub RetrieveAllOwnersOfGroup { } sub RemoveOwnerFromGroup { } sub _build_group_entry { my $self = shift; my $xml = shift; my $entry = VUser::Google::Groups::GroupEntry->new(); $entry->GroupId($xml->{'apps:property'}{'groupId'}{'value'}); $entry->GroupName($xml->{'apps:property'}{'groupName'}{'value'}); $entry->Description($xml->{'apps:property'}{'description'}{'value'}); $entry->EmailPermission($xml->{'apps:property'}{'emailPermission'}{'value'}); return $entry; } no Moose; __PACKAGE__->meta->make_immutable; 1; VUser-Google-Api-1.0.1/lib/VUser/Google/Groups/GroupEntry.pm0000644000175000017500000000120211570726502023361 0ustar rbsmithrbsmithpackage VUser::Google::Groups::GroupEntry; use warnings; use strict; our $VERSION = '0.2.0'; use Moose; has 'GroupId' => (is => 'rw', isa => 'Str | Undef'); has 'GroupName' => (is => 'rw', isa => 'Str | Undef'); has 'Description' => (is => 'rw', isa => 'Str | Undef'); has 'EmailPermission' => (is => 'rw', isa => 'Str | Undef'); sub as_hash { my $self = shift; my %hash = ( groupId => $self->GroupId, groupName => $self->GroupName, description => $self->Description, emailPermission => $self->EmailPermission, ); return %hash; } no Moose; __PACKAGE__->meta->make_immutable; 1; VUser-Google-Api-1.0.1/lib/VUser/Google/ProvisioningAPI.pm0000644000175000017500000001022511570726511023011 0ustar rbsmithrbsmithpackage VUser::Google::ProvisioningAPI; use warnings; use strict; # Copyright (C) 2007 Randy Smith, perlstalker at vuser dot org # Copyright (C) 2006 by Johan Reinalda, johan at reinalda dot net use vars qw($VERSION); our $VERSION = '0.25'; use Carp; sub new { my ($obj, $domain, $admin, $passwd, $api_version) = @_; my $class = ref($obj) || $obj; # If the API version is not specified assume 1.0 to remain compatible # with VUser::Google::ProvisioningAPI 0.11 if (not defined $api_version or $api_version eq '1.0') { require VUser::Google::ProvisioningAPI::V1_0; return VUser::Google::ProvisioningAPI::V1_0->new($domain, $admin, $passwd); } elsif ($api_version eq '2.0') { require VUser::Google::ProvisioningAPI::V2_0; return VUser::Google::ProvisioningAPI::V2_0->new($domain, $admin, $passwd); } else { croak "Unknown API version: $api_version"; } } #print out debugging to STDERR if debug is set sub dprint { my $self = shift(); my($text) = shift if (@_); if( $self->{debug} and defined ($text) ) { print STDERR $text . "\n"; } } 1; __END__ =head1 NAME VUser::Google::ProvisioningAPI - Perl module that implements the Google Apps for Your Domain Provisioning API =head1 SYNOPSIS use VUser::Google::ProvisioningAPI; my $google = new VUser::Google::ProvisioningAPI($domain,$admin,$password, $api_version); $google->CreateAccount($userName, $firstName, $lastName, $password); $google->RetrieveAccount($userName); =head1 REQUIREMENTS VUser::Google::ProvisioningAPI requires the following modules to be installed: =over =item C =item C =item C =item C =back =head1 DESCRIPTION B VUser::Google::ProvisioningAPI provides a simple interface to the Google Apps for Your Domain Provisioning API. It uses the C module for the HTTP transport, and the C module for the HTTP request and response. =head1 CONSTRUCTOR new ( $domain, $admin, $adminpassword [,$api_version] ) This is the constructor for a new VUser::Google::ProvisioningAPI object. $domain is the domain name registered with Google Apps For Your Domain, $admin is an account in the above domain that has the right to manage that domain, $adminpassword is the password for that account and $api_version is the version of the Google Provisioning API you wish to use. At this time, only '1.0' and '2.0' are supported. Note that the constructor will NOT attempt to perform the 'ClientLogin' call to the Google Provisioning API. Authentication happens automatically when the first API call is performed. The token will be remembered for the duration of the object, and will be automatically refreshed as needed. If you want to verify that you can get a valid token before performing any operations, follow the constructor with a call to IsAuthenticated() as such: print "Authentication OK\n" unless not $google->IsAuthenticated(); =head1 METHODS The methods provided by the object will vary based on the version of the API. Please see the perldocs for specific version you are using. For example, C. =head1 EXPORT None by default. =head1 SEE ALSO For support, see the Google Group at http://groups.google.com/group/apps-for-your-domain-apis L L =head1 BUGS Please report bugs or feature requests at http://code.google.com/p/vuser/issues/list. =head1 AUTHORS Johan Reinalda, johan at reinalda dot net Randy Smith, perlstalker at vuser dot net =head1 COPYRIGHT AND LICENSE Copyright (C) 2006 by Johan Reinalda, johan at reinalda dot net Copyright (C) 2007 Randy Smith, perlstalker and vuser dot org This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. If you make useful modification, kindly consider emailing then to me for inclusion in a future version of this module. =cut VUser-Google-Api-1.0.1/MANIFEST0000644000175000017500000000205011570731200015522 0ustar rbsmithrbsmithCOPYING INSTALL lib/VUser/Google/ApiProtocol.pm lib/VUser/Google/ApiProtocol/V2_0.pm lib/VUser/Google/EmailSettings.pm lib/VUser/Google/EmailSettings/V2_0.pm lib/VUser/Google/Groups.pm lib/VUser/Google/Groups/GroupEntry.pm lib/VUser/Google/Groups/V2_0.pm lib/VUser/Google/Provisioning.pm lib/VUser/Google/Provisioning/UserEntry.pm lib/VUser/Google/Provisioning/V2_0.pm lib/VUser/Google/ProvisioningAPI.pm lib/VUser/Google/ProvisioningAPI/V1_0.pm lib/VUser/Google/ProvisioningAPI/V2_0.pm lib/VUser/Google/ProvisioningAPI/V2_0/EmailListEntry.pm lib/VUser/Google/ProvisioningAPI/V2_0/EmailListRecipientEntry.pm lib/VUser/Google/ProvisioningAPI/V2_0/NicknameEntry.pm lib/VUser/Google/ProvisioningAPI/V2_0/UserEntry.pm Makefile.PL MANIFEST This list of files README.Google-ProvisioningAPI t/run.t t/tests/My/Test/Class.pm t/tests/Test/VUser/Google/Groups.pm t/tests/Test/VUser/Google/Groups/V2_0.pm t/tests/Test/VUser/Google/Provisioning.pm t/tests/Test/VUser/Google/Provisioning/V2_0.pm META.yml Module meta-data (added by MakeMaker) VUser-Google-Api-1.0.1/Makefile.PL0000644000175000017500000000262511570726511016364 0ustar rbsmithrbsmith#!/usr/bin/perl # Copyright 2005 Randy Smith # $Id: Makefile.PL,v 1.1 2007-09-17 16:13:05 perlstalker Exp $ use ExtUtils::MakeMaker; WriteMakefile( NAME => 'VUser-Google-Api', VERSION_FROM => 'lib/VUser/Google/ApiProtocol.pm', INSTALLSCRIPT => '$(PREFIX)/sbin', PREREQ_PM => { 'Carp' => 0, 'LWP::UserAgent' => 0, 'HTTP::Request' => 0, 'Encode' => 0, 'XML::Simple' => 0, 'Crypt::SSLeay' => 0, }, # e.g., Module::Name => 1.1 ); __END__ =head1 AUTHOR Randy Smith =head1 LICENSE This file is part of VUser-Google-ProvisioningAPI. VUser-Google-ProvisioningAPI is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. VUser-Google-ProvisioningAPI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with VUser-Google-ProvisioningAPI; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut VUser-Google-Api-1.0.1/COPYING0000644000175000017500000004313111570726511015442 0ustar rbsmithrbsmith GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. VUser-Google-Api-1.0.1/README.Google-ProvisioningAPI0000755000175000017500000000244511570726511021526 0ustar rbsmithrbsmithVUser-Google-ProvisioningAPI version 0.20 =================================== VUser::Google::ProvisioningAPI - module that implements the Google Apps for Your Domain Provisioning API For a complete description of the Google API, see http://code.google.com/apis/apps-for-your-domain/google_apps_provisioning_api_v1.0_reference.html or http://code.google.com/apis/apps/gdata_provisioning_api_v2.0_reference.html VERSION v0.21 Fix bug in RetrieveAllUsers() and some i18n fixes. v0.20 Add support for Google Provisioning API 2.0. Moved into VUser:: to prevent conflicts with upstream releases. v0.11 is a small bug-fix and Google API changes release, see Changes for more v0.1 is the initial release INSTALLATION To install this module type the following: perl Makefile.PL make make test make install DEPENDENCIES This module requires these other modules and libraries: LWP::UserAgent HTTP::Request Encode XML::Simple COPYRIGHT AND LICENCE Copyright (C) 2006 by Johan Reinalda Copyright (C) 2007 by Randy Smith This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available. VUser-Google-Api-1.0.1/INSTALL0000644000175000017500000000202511570726511015435 0ustar rbsmithrbsmithRequired Modules: VUser-Google-ProvisioningAPI requires the following Perl modules that are not included with Perl: - Carp - LWP::UserAgent - HTTP::Request - Encode - XML::Simple Other vuser extensions may require other Perl modules. Please see the documentation included with those extensions for additional requirements. Basic Installation: perl Makefile.PL make make test make install That will install vuser on your system in /usr (or /usr/local) by default. You can define a different install path by setting PREFIX when running Makefile.PL. For example, to install vuser in your home directory perl Makefile.PL PREFIX=~ or to install in /opt perl Makefile.PL PREFIX=/opt The install script will create lib, share and sbin directories in PREFIX. If you install in a PREFIX that is not included in perl's default library paths, you will need to set PERL5LIB to include the new directory. For example, if you set PREFIX=/opt, you might add this to your .profile (or /etc/profile). export PERL5LIB=/opt/lib/perl5/site_perl/ VUser-Google-Api-1.0.1/t/0000755000175000017500000000000011570731200014637 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/run.t0000644000175000017500000000216511570726502015645 0ustar rbsmithrbsmith#!/usr/bin/perl use warnings; use strict; use Test::Most tests => 3, 'die'; use FindBin; use lib ("$FindBin::Bin/../lib"); my $gapps_domain = $ENV{GAPPS_DOMAIN}; my $gapps_user = $ENV{GAPPS_ADMIN}; my $gapps_passwd = $ENV{GAPPS_PASSWD}; if (not $gapps_domain and not $gapps_user and not $gapps_passwd ) { warn "GAPPS_DOMAIN, GAPPS_ADMIN or GAPPS_PASSWD not set\n"; exit; } warn "$gapps_domain, $gapps_user, $gapps_passwd"; use VUser::Google::ProvisioningAPI::V2_0; ## Create google object my $google = VUser::Google::ProvisioningAPI::V2_0->new( $gapps_domain, $gapps_user, $gapps_passwd ); isa_ok($google, 'VUser::Google::ProvisioningAPI::V2_0'); $google->{debug} = 1; ## IsAuthenticated is($google->IsAuthenticated, 1, 'Authentication succeeded'); ## Create user TODO: { local $TODO = "test not written"; } ## Test setting password my $user = VUser::Google::ProvisioningAPI::V2_0::UserEntry->new(); $user->Password('Foo"bar'); my $entry = $google->UpdateUser('account10', $user); print STDERR $google->{result}{reason} if not $entry; isa_ok($entry, 'VUser::Google::ProvisioningAPI::V2_0::UserEntry'); VUser-Google-Api-1.0.1/t/tests/0000755000175000017500000000000011570731200016001 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/tests/Test/0000755000175000017500000000000011570731200016720 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/tests/Test/VUser/0000755000175000017500000000000011570731200017764 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/tests/Test/VUser/Google/0000755000175000017500000000000011570731200021200 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/tests/Test/VUser/Google/Provisioning/0000755000175000017500000000000011570731200023666 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/tests/Test/VUser/Google/Provisioning/V2_0.pm0000644000175000017500000001351211570726502024745 0ustar rbsmithrbsmithpackage Test::VUser::Google::Provisioning::V2_0; use warnings; use strict; use Test::Most; use base 'Test::VUser::Google::Provisioning'; use vars qw($SKIP_LONG_TESTS); sub CreateUser : Tests(12) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); can_ok $api, 'CreateUser'; my $user = $test->get_test_user; my $res = $api->CreateUser( userName => $user, givenName => 'Test', familyName => 'User', password => 'testing', quota => 2048, changePasswordAtNextLogin => 1, ); isa_ok $res, 'VUser::Google::Provisioning::UserEntry', '... and the account was created'; is $res->UserName, $user, "... and the username is $user"; ## Retrieve Test can_ok $api, 'RetrieveUser'; $res = $api->RetrieveUser($user); isa_ok $res, 'VUser::Google::Provisioning::UserEntry', '... and the account was retrieved'; is $res->GivenName, 'Test', '... and retrieved given name matches'; is $res->FamilyName, 'User', '... and retrieved family name matches'; TODO: { local $TODO = 'How to check if quota updates are disabled?'; is $res->Quota, '2048', '... and retrieved quota matches'; } is $res->ChangePasswordAtNextLogin, 1, '... and retrieved change pw matches'; ## clean up can_ok $api, 'DeleteUser'; my $rc = $api->DeleteUser($res->UserName); is $rc, 1, '... and delete reports successful'; $res = $api->RetrieveUser($user); ok !defined $res, '... and there\'s nothing to retrieve'; } sub RetrieveUsers : Tests(5) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); can_ok $api, 'RetrieveUsers'; can_ok $api, 'RetrieveAllUsers'; my $num_users = 110; SKIP: { if ($Test::VUser::Google::SKIP_LONG_TESTS) { skip "Skipping long tests at user request.", 3; } ## Create 110 test users note "Creating $num_users test users. This will take a while."; my $user = $test->get_test_user; print STDERR "Creating test users: "; foreach my $i (1 .. $num_users) { print STDERR "." if $i%10 == 0; my $res = $api->CreateUser( userName => $user.".$i", givenName => 'Test', familyName => 'User', password => 'testing', quota => 2048, changePasswordAtNextLogin => 1, ); } print "\n"; ## Fetch first page of users my %results = $api->RetrieveUsers; is @{ $results{'entries'} }, 100, '... and we have 100 users'; my $next = $results{next}; ## Fetch second page of users %results = $api->RetrieveUsers($next); is $results{'entries'}[0]->UserName, $next, '... and the first result of the second page is the "next" from the first page'; ## Retrieve all users my @entries = $api->RetrieveAllUsers; TODO: { local $TODO = 'How many users already existed?'; ok @entries >= $num_users+1, '... and there are the expected number of users'; } ## Delete test users note "Deleting $num_users test users. This will also take a while."; print STDERR "\nDeleting test users: "; foreach my $i (1 .. $num_users) { print STDERR "." if $i%10 == 0; my $rc = $api->DeleteUser($user.".$i"); } } # END SKIP } sub UpdateUser : Tests(7) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); can_ok $api, 'UpdateUser'; my $user = $test->get_test_user; my $entry = $api->CreateUser( userName => $user, givenName => 'Test', familyName => 'User', password => 'testing', quota => 2048, changePasswordAtNextLogin => 1, ); my $updated = $api->UpdateUser( userName => $user, givenName => 'GName' ); is $updated->GivenName, 'GName', '... and given name matches'; $updated = $api->UpdateUser( userName => $user, familyName => 'Fname', ); is $updated->FamilyName, 'Fname', '... and family name matches'; $updated = $api->UpdateUser( userName => $user, suspended => 1, ); is $updated->Suspended, 1, '... and suspended matches'; $updated = $api->UpdateUser( userName => $user, quota => 1024, ); TODO: { local $TODO = 'May not be allowed to change quotas.'; is $updated->Quota, 1024, '... and quota matches'; } $updated = $api->UpdateUser( userName => $user, changePasswordAtNextLogin => 0, ); is $updated->ChangePasswordAtNextLogin, 0, '... and changePasswordAtNextLogin matches'; can_ok $api, 'ChangePassword'; TODO: { local $TODO = 'How can we test if setting the password actually worked?'; # Use ClientLogin API to test Auth? # http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html $updated = $api->ChangePassword( $user, 'new-password', ); $updated = $api->ChangePassword( $user, 'd27117a019717502efe307d110f5eb3d', 'MD5' ); $updated = $api->ChangePassword( $user, '51eea05d46317fadd5cad6787a8f562be90b4446', 'SHA-1' ); } my $rc = $api->DeleteUser($user); } sub RenameUser : Tests(6) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); can_ok $api, 'RenameUser'; my $user = $test->get_test_user; my $old_user = $api->CreateUser( userName => $user, givenName => 'Test', familyName => 'User', password => 'testing', ); my $new_user = $api->RenameUser($user, $user.'.new'); isa_ok $new_user, 'VUser::Google::Provisioning::UserEntry', '... and the account was renamed'; is $new_user->UserName, $user.'.new', '... and the user name has been updated'; ## Double-check that settings match is $new_user->GivenName, $old_user->GivenName, '... and the given names match'; is $new_user->FamilyName, $old_user->FamilyName, '... and the family names match'; is $new_user->Quota, $old_user->Quota, '... and the quotas match'; my $rc = $api->DeleteUser($user.'.new'); } 1; VUser-Google-Api-1.0.1/t/tests/Test/VUser/Google/Provisioning.pm0000644000175000017500000000127411570726502024241 0ustar rbsmithrbsmithpackage Test::VUser::Google::Provisioning; use warnings; use strict; use Test::Most; use base 'My::Test::Class'; my $acct; sub constructor : Tests(3) { my $test = shift; my $class = $test->class; can_ok $class, 'new'; ok my $api = $class->new(google => $test->create_google), '... and the constructor should succeed'; isa_ok $api, $class, '... and the object it returns'; } sub get_test_user { my $self = shift; if (1 || not defined $acct) { my @time = localtime; $acct = sprintf ( 'test.%04d.%02d.%02d.%02d.%02d.%02d', $time[5]+1900, $time[4]+1, $time[3], $time[2], $time[1], $time[0] ); } return $acct; } 1; VUser-Google-Api-1.0.1/t/tests/Test/VUser/Google/Groups.pm0000644000175000017500000000127511570726502023033 0ustar rbsmithrbsmithpackage Test::VUser::Google::Groups; use warnings; use strict; use Test::Most; use base 'My::Test::Class'; my $acct; sub constructor : Tests(3) { my $test = shift; my $class = $test->class; can_ok $class, 'new'; ok my $api = $class->new(google => $test->create_google), '... and the constructor should succeed'; isa_ok $api, $class, '... and the object it returns'; } sub get_test_group { my $self = shift; if (1 || not defined $acct) { my @time = localtime; $acct = sprintf ( 'test.group.%04d.%02d.%02d.%02d.%02d.%02d', $time[5]+1900, $time[4]+1, $time[3], $time[2], $time[1], $time[0] ); } return $acct; } 1; VUser-Google-Api-1.0.1/t/tests/Test/VUser/Google/Groups/0000755000175000017500000000000011570731200022457 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/tests/Test/VUser/Google/Groups/V2_0.pm0000644000175000017500000001261611570726502023542 0ustar rbsmithrbsmithpackage Test::VUser::Google::Groups::V2_0; use warnings; use strict; use Test::Most; use base 'Test::VUser::Google::Groups'; sub CreateGroup : Tests(8) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); can_ok $api, 'CreateGroup'; my $group = $test->get_test_group; my $entry = $api->CreateGroup( groupId => $group, groupName => "test group $group", description => 'test group descr', emailPermission => 'Domain', ); isa_ok $entry, 'VUser::Google::Groups::GroupEntry', '... and the create succeeded'; is $entry->GroupId, $group, '... and group id matches'; is $entry->GroupName, "test group $group", '... and group name matches'; is $entry->Description, 'test group descr', '... and description matches'; is $entry->EmailPermission, 'Domain', '... and email permission matches'; ## Clean up can_ok $api, 'DeleteGroup'; ok $api->DeleteGroup($group), '... and delete suceeded'; } sub RetrieveGroup : Tests(6) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); can_ok $api, 'UpdateGroup'; my $group = $test->get_test_group; my $entry = $api->CreateGroup( groupId => $group, groupName => "test group $group", description => 'test group descr', emailPermission => 'Domain', ); my $new_entry = $api->RetrieveGroup($group); is $new_entry->GroupId, $group.'@'.$api->google->domain, '... and group id matches'; is $new_entry->GroupName, "test group $group", '... and group name matches'; is $new_entry->Description, 'test group descr', '... and description matches'; is $new_entry->EmailPermission, 'Domain', '... and email permission matches'; ok $api->DeleteGroup($group), '... and delete suceeded'; } sub UpdateGroup : Tests(7) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); can_ok $api, 'UpdateGroup'; my $group = $test->get_test_group; my $entry = $api->CreateGroup( groupId => $group, groupName => "test group $group", description => 'test group descr', emailPermission => 'Domain', ); my $new_entry = $api->UpdateGroup( groupId => $group, #newGroupId => $group.'.new', groupName => "test group $group.new", description => 'test group descr new', emailPermission => 'Member', ); # Can't rename groups #$entry = $api->RetrieveGroup($group); #ok !defined $entry, # '... and the old group is gone'; isa_ok $new_entry, 'VUser::Google::Groups::GroupEntry', '... and the create succeeded'; # Can't rename group #is $new_entry->GroupId, $group.'.new' #.'@'.$api->google->domain,, # '... and group id matches'; is $new_entry->GroupName, "test group $group.new", '... and group name matches'; is $new_entry->Description, 'test group descr new', '... and description matches'; is $new_entry->EmailPermission, 'Member', '... and email permission matches'; ok $api->DeleteGroup($group), '... and delete suceeded'; } sub AddMemberToGroup : Tests(8) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); ## create test group my $group = $test->get_test_group; my $entry = $api->CreateGroup( groupId => $group, groupName => "test group $group", description => 'test group descr', emailPermission => 'Domain', ); isa_ok $entry, 'VUser::Google::Groups::GroupEntry', '... and the create succeeded'; ## add member to group can_ok $api, 'AddMemberToGroup'; $api->AddMemberToGroup( 'member' => 'test@example.com', 'group' => $group ); ## get group members TODO: { local $TODO = '...'; is 0, '2048', '... and member is in the group'; } ## remove group member TODO: { local $TODO = 'RemoveMemberOfGroup not written'; can_ok $api, 'RemoveMemberOfGroup'; ok $api->RemoveMemberOfGroup( 'member' => 'test@example.com', 'group' => $group ); } ## get group members, member deleted? TODO: { local $TODO = '...'; is 0, '2048', '... and member is in the group'; } ## delete group can_ok $api, 'DeleteGroup'; ok $api->DeleteGroup($group), '... and delete suceeded'; } sub RetrieveAllGroupsInDomain : Tests(13) { my $test = shift; my $class = $test->class; my $api = $class->new(google => $test->create_google); can_ok $api, 'RetrieveAllGroupsInDomain'; my @c_groups = (); # created groups my $base_group = $test->get_test_group; for my $i (0 .. 3) { my $entry = $api->CreateGroup( groupId => $base_group.$i, groupName => "test group $base_group$i", description => 'test group descr'.$i, emailPermission => 'Domain', ); push @c_groups, $entry; } my @r_groups = $api->RetrieveAllGroupsInDomain; for my $i (0 .. 3) { is $r_groups[$i]->GroupId, $c_groups[$i]->GroupId.'@'.$api->google->domain, "... [$i] groupId matches"; is $r_groups[$i]->GroupName, $c_groups[$i]->GroupName, "... [$i] groupName matches"; is $r_groups[$i]->Description, $c_groups[$i]->Description, "... [$i] description matches"; } ## Clean up foreach my $group (@c_groups) { $api->DeleteGroup($group->GroupId); } } 1; VUser-Google-Api-1.0.1/t/tests/My/0000755000175000017500000000000011570731200016366 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/tests/My/Test/0000755000175000017500000000000011570731200017305 5ustar rbsmithrbsmithVUser-Google-Api-1.0.1/t/tests/My/Test/Class.pm0000644000175000017500000000316511570726502020726 0ustar rbsmithrbsmithpackage My::Test::Class; use warnings; use strict; use Test::Most; use base qw(Test::Class Class::Data::Inheritable); use vars qw($gapps_domain $gapps_admin $gapps_passwd); BEGIN { $gapps_domain = $ENV{GAPPS_DOMAIN}; $gapps_admin = $ENV{GAPPS_ADMIN}; $gapps_passwd = $ENV{GAPPS_PASSWD}; print STDERR "Domain: $gapps_domain\n"; if (not $gapps_domain and not $gapps_admin and not $gapps_passwd ) { plan skip_all => "Set the GAPPS_DOMAIN, GAPPS_ADMIN or GAPPS_PASSWD environment variables to run tests."; exit; } if (defined $ENV{GAPPS_SKIP_LONG_TESTS}) { $Test::VUser::Google::SKIP_LONG_TESTS = $ENV{GAPPS_SKIP_LONG_TESTS}?1:0; } if (not defined $Test::VUser::Google::SKIP_LONG_TESTS) { $Test::VUser::Google::SKIP_LONG_TESTS = 0; print STDERR "\nSome of the tests can take a long time to complete."; print STDERR " (20 minutes or more)\n"; print STDERR "Would you like to skip these tests? [y/N]: "; my $response = ; $Test::VUser::Google::SKIP_LONG_TESTS = 1 if $response =~ /^y/i; } __PACKAGE__->mk_classdata('class'); } INIT { Test::Class->runtests; } sub startup : Tests( startup => 1 ) { my $test = shift; ( my $class = ref $test ) =~ s/^Test:://; return ok 1, "$class loaded" if $class eq __PACKAGE__; use_ok $class or die; $test->class($class); } sub create_google { use VUser::Google::ApiProtocol::V2_0; my $google = VUser::Google::ApiProtocol::V2_0->new( domain => $ENV{GAPPS_DOMAIN}, admin => $ENV{GAPPS_ADMIN}, password => $ENV{GAPPS_PASSWD}, debug => $ENV{GAPPS_DEBUG} || 0, ); return $google; } 1; VUser-Google-Api-1.0.1/META.yml0000644000175000017500000000114111570731200015642 0ustar rbsmithrbsmith--- #YAML:1.0 name: VUser-Google-Api version: 1.0.1 abstract: ~ author: [] license: unknown distribution_type: module configure_requires: ExtUtils::MakeMaker: 0 build_requires: ExtUtils::MakeMaker: 0 requires: Carp: 0 Crypt::SSLeay: 0 Encode: 0 HTTP::Request: 0 LWP::UserAgent: 0 XML::Simple: 0 no_index: directory: - t - inc generated_by: ExtUtils::MakeMaker version 6.56 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4