File Coverage

blib/lib/CAS/DB.pm
Criterion Covered Total %
statement 44 187 23.5
branch 6 106 5.6
condition 5 39 12.8
subroutine 8 15 53.3
pod 5 6 83.3
total 68 353 19.2


line stmt bran cond sub pod time code
1             package CAS::DB;
2              
3 6     6   4188 use strict;
  6         17  
  6         437  
4              
5             =head1 NAME
6              
7             CAS::DB - DBI wrapper which adds a few CAS specific methods.
8              
9             =head1 VERSION
10              
11             Version 0.40_02
12              
13             =cut
14              
15             our $VERSION = '0.40_02';
16              
17             =head1 SYNOPSIS
18              
19             Connect to CAS database.
20              
21             use CAS::DB;
22             my $dbh = CAS::DB->connectDB(\%params);
23            
24             Though you shouldn't be connecting directly. Instead, load the CAS::Config data
25             and get the dbh from there.
26              
27             use CAS::Config;
28             my $HR_config = CAS::Config->load({CLIENT_ID => n});
29             my $dbh = $HR_config->{dbh};
30              
31              
32             =head1 ABSTRACT
33              
34             Wraps the DBI module, extending the database handle with some CAS specific
35             methods. This module is not intemded to be used directly - _config.pm
36             makes the connection using paramters from the CAS.yaml configuration.
37              
38             =cut
39              
40 6     6   39 use vars qw($AUTOLOAD);
  6         13  
  6         288  
41              
42 6     6   6580 use Data::Dumper;
  6         50461  
  6         565  
43 6     6   61 use Scalar::Util qw(blessed);
  6         14  
  6         523  
44 6     6   15354 use DBI;
  6         130015  
  6         467  
45              
46             # otherwise constants don't get exported
47             #use base qw(CAS::Messaging);
48 6     6   4087 use CAS::Messaging;
  6         20  
  6         1365  
49             our @ISA = qw(CAS::Messaging);
50 6     6   42 use Carp qw(cluck confess croak carp);
  6         11  
  6         18318  
51              
52              
53             =head1 METHODS
54              
55              
56             =head2 connectDB
57              
58             Wrapper for DBI->connect. Mainly does some configuration checking and if the
59             connection attempt fails will try every three seconds ten times.
60              
61             PARAMETERS:
62              
63             user: Username to connect to the database with.
64              
65             password: Password for user.
66              
67             server: Type of database server. Defaults to mysql.
68              
69             host: Host to connect to. Defaults to localhost.
70              
71             =cut
72             sub connectDB {
73 8     8 1 23 my $proto = shift;
74 8   33     65 my $class = ref($proto) || $proto;
75 8         22 my $self = {};
76 8         20 my $HR_params = shift;
77 8 50       43 croak("Parameters not passed as a hashref")
78             unless ref($HR_params) eq 'HASH';
79            
80 8 50       52 my $user_name = $HR_params->{user} or die 'No username provided';
81 8 50       36 my $password = $HR_params->{password} or die 'No password provided';
82 8   50     76 my $server = $HR_params->{server} || 'mysql';
83 8   50     49 my $host = $HR_params->{host} || $ENV{DBHost} || 'localhost';
84 8         22 my $db = $HR_params->{database};
85            
86            
87             #handle params as nec. such as setting debug or changing env. variables
88 8   50     94 my $DEBUG = $HR_params->{'DEBUG'} || 0;
89 8 50       28 $^W++ if $DEBUG;
90 8 50 0     31 (require diagnostics && import diagnostics) if $DEBUG >= 2;
91            
92 8         25 $self->{'_created'} = 1;
93 8         25 $self->{'db'} = $db;
94 8         29 $self->{debug} = $DEBUG;
95            
96 8         33 my $dsn = "DBI:$server:$db:$host";
97 8         20 my $dbh = '';
98 8         15 my $attemp_count = 1;
99 8   50     77 my $atrb = $HR_params->{DBIconnectAttributes} || { PrintError => 1 };
100 8 50       37 warn "DBI->connect($dsn,$user_name,$password,$atrb)\n" if $DEBUG >= 2;
101            
102             # connect to database
103 8         106 CONNECT: {
104 8         17 $dbh = DBI->connect($dsn,$user_name,$password,$atrb);
105 0 0         unless ($dbh) {
106 0           warn "Have no connection to DB ($dsn,$user_name), retrying in 3";
107 0           sleep(3);
108 0           $attemp_count++;
109 0 0         redo CONNECT unless $attemp_count > 10;
110             } # no connection
111             } # CONNECT control block
112            
113             # die if fail - catch with eval
114 0 0         die "Failed to get connection $dbh after $attemp_count tries: $DBI::errstr"
115             unless $dbh;
116            
117 0           $self->{dbh} = $dbh;
118            
119             # OK, lets internalize any other DB's provided, such as DBAdmin,
120             # DBFooBar etc.
121 0           foreach my $field (keys %{$HR_params}) {
  0            
122             #warn("Setting DB's, field = $field\n");
123 0 0         $self->{$field} = $HR_params->{$field}
124             if $field =~ /DB$/;
125             #warn("Set self->{$field} = $self->{$field}\n");
126             } # foreach param
127            
128 0           my $obj = bless ($self,$class);
129 0           $obj->_set_result(CREATED,"CAS DB object sucesfully initiatied");
130 0           return $obj;
131             } # end of sub ConnectDB()
132              
133              
134             =head2 allowed
135              
136             Does the user have the requested permission on the indicated resource. Return
137             value is true (actually returns the numeric value of the mask) if allowed, null
138             (uundef) if not, 0 on error. Call $DBH->error to see any error messages.
139              
140             This method will check for permissions by both user id ad group memberships.
141             However it is important to remember that permission granted in any grants
142             permission, and individual user permision is checked first.
143              
144             PARAMS:
145              
146             USER: The database ID of the user.
147              
148             RESOURCE: The resource we are checking. Could be a database table, a file (such
149             as a CGI or data archive), a port - whatever.
150              
151             CLIENT: The client ID or domain from which this request is being made.
152              
153             PERMISSION: This is the type of action you want to check if the user has
154             permission for relative to the RESOURCE. The allowed values are read, modify,
155             create and delete. Create refers to permision to create a new record which
156             uses the refered to resource as a foreign key, or is under the refered resource
157             'tree'.
158              
159             OPTIONS:
160              
161             MASK: This is an integer mask of permissions to be checked for the specified
162             RESOURCE. This can optionaly be used instead of PERMISSION, and is the only
163             way to specify requests on more than one type of permission at the same time.
164             The Values are 8 = read, 4 = modify, 2 = create, 1 = delete. To check for
165             multiple permissions at the same time simply sum all the permissions you want
166             to check. For example, to check for read and modify permision, provide 12 (8+4)
167             as the value for MASK. MASK overides PERMISSION if both are specified.
168              
169             MATCHKEY: A matchkey can be used to specify a specific element or key
170             match required. For example, RESOURCE my specify a particular table in a
171             database, with MATCHLEY specifying the primary key match required. Or if
172             RESOURCE was a web page, MATCHKEY may indicate a specific form element.
173              
174             Examples:
175              
176             # can place orders using fund 8887-009500
177             my $can_do = $dbh->allowed({USER => 12345, RESOURCE => 'DNAcoreAdmin.Fund',
178             MATCHKEY => '8887,009500', PERMISSION => create});
179              
180             # can view oligo OD QC tool CGI
181             my $can_do = $dbh->allowed({RESOURCE => 'cgi-bin/synthesis/oligoOD',
182             USER => 12345, PERMISSION => 'read'});
183              
184             # can delete results file
185             my $can_do = $dbh->allowed({RESOURCE => 'sequencing/results/MK453GF67.seq',
186             MASK => 1, USER => 12345);
187              
188             To check the results
189             unless($can_do) {
190             if ($dbh->response_is('FORBIDDEN')) {
191             # give user the bad news
192             } # user does not have permission
193             else {
194             die "Problem checking permissions: $dbh->messages";
195             } # otherwise something went wrong
196             } # user can't
197              
198             =cut
199             sub allowed {
200 0     0 1   my $self = shift;
201 0 0         $self->error("Not a method call") unless blessed($self);
202 0 0         $self->_clear_result unless __PACKAGE__ eq caller;
203            
204 0           my $HR_params = shift;
205 0 0         $self->error("Parameters not passed as a hashref")
206             unless ref($HR_params) eq 'HASH';
207 0   0       my $debug = $HR_params->{DEBUG} || $self->{debug} || 0;
208 0           my $dbh = $self->{dbh};
209            
210             # make sure we have required argumants
211 0 0 0       unless ($HR_params->{USER} && $HR_params->{USER} =~ /^\d+$/) {
212 0           $self->_set_result(BAD_REQUEST,"No user ID provided.");
213 0           return undef;
214             } # userdat hash required
215            
216 0 0         unless ($HR_params->{RESOURCE}) {
217 0           $self->_set_result(BAD_REQUEST,
218             "Resource to check against is required.");
219 0           return undef;
220             } # RESOURCE required
221            
222 0 0 0       unless ($HR_params->{CLIENT} && $HR_params->{CLIENT} =~ /^\d+$/) {
223 0           $self->_set_result(BAD_REQUEST,
224             "The client ID for which this resource applies is required.");
225 0           return undef;
226             } # client required
227            
228 0           my %from_text_mask = (read => 8, modify => 4, create => 2, delete => 1);
229 0 0 0       if ($HR_params->{MASK} && $HR_params->{MASK} =~ /^\d{1,2}$/) {
    0 0        
230 0           $self->_set_result(CONTINUE, "MASK is a number, continuing");
231             } # if MASK
232             elsif ($HR_params->{PERMISSION}
233             && $from_text_mask{$HR_params->{PERMISSION}}) {
234            
235 0           $HR_params->{MASK} = $from_text_mask{$HR_params->{PERMISSION}};
236 0           $self->_set_result(CONTINUE, "MASK translated from PERMISSION, "
237             . "continuing");
238             } # if text permission
239             else {
240 0           $self->_set_result(BAD_REQUEST,
241             "Need to know what permission to compare against. Either"
242             . "PERMISSION or MASK was missing or invalid");
243 0           return undef;
244             } # else can't continue
245            
246            
247             # prepare params for use in SQL
248 0   0       $HR_params->{MATCHKEY} ||= '%';
249 0           my $resource = $dbh->quote($HR_params->{RESOURCE});
250 0           my $key = $dbh->quote($HR_params->{MATCHKEY});
251 0           my $mask = $HR_params->{MASK};
252            
253             # check for permission by user id
254 0           my $user_qr = "SELECT ModTime
255             FROM Permissions
256             WHERE Client = $HR_params->{CLIENT} AND User = $HR_params->{USER}
257             AND Resource = $resource AND MatchKey LIKE $key
258             AND (Permissions & $mask) = $mask";
259 0 0         $self->gripe("User Query: $user_qr\n") if $debug > 1;
260            
261 0           my $has_perm = $dbh->selectrow_array($user_qr);
262 0 0         if ($DBI::err) {
263 0           $self->_set_result(ERROR,
264             "Problem checking permission by user id: $DBI::errstr");
265 0           return undef;
266             } # if dbi error
267            
268 0 0         if ($has_perm) {
269 0           $self->_set_result(OK, "Permision granted on user");
270 0           return $has_perm;
271             } # if allowed
272            
273             # user did not have permision directly, now check if any groups
274             # grant requested permission
275 0           my $AR_groups = $dbh->selectcol_arrayref("SELECT GroupID FROM Groups
276             WHERE User = $HR_params->{USER}");
277 0 0         if ($DBI::err) {
278 0           $self->_set_result(ERROR,
279             "Problem getting users groups: $DBI::errstr");
280 0           return undef;
281             } # if dbi error
282 0 0         unless (@{$AR_groups}) {
  0            
283 0           $self->_set_result(ERROR,
284             "User $HR_params->{USER} is not a member of any groups");
285 0           return undef;
286             } # no groups!?!
287            
288 0           my $grp_set = "'" . join(",",@{$AR_groups}) . "'";
  0            
289 0           my $group_qr = "SELECT ModTime
290             FROM Permissions
291             WHERE Client = $HR_params->{CLIENT} AND FIND_IN_SET(GroupID,$grp_set)
292             AND Resource = $resource
293             AND MatchKey LIKE $key AND (Permissions & $mask) = $mask";
294 0 0         $self->gripe("Group Query: $group_qr\n") if $debug > 1;
295            
296 0           $has_perm = $dbh->selectrow_array($group_qr);
297 0 0         if ($DBI::err) {
298 0           $self->_set_result(ERROR,
299             "Problem checking permission by group: $DBI::errstr");
300 0           return undef;
301             } # if dbi error
302            
303 0 0         if ($has_perm) {
304 0           $self->_set_result(OK, "Permision granted on group");
305 0           return $has_perm;
306             } # if allowed
307            
308 0 0         $self->gripe("got to end of allowed and got no permisions -\nUser:\n"
309             . "\t$user_qr\nGroup:\n\t$group_qr\n") if $debug;
310             # nope - permission denied
311            
312 0           $self->_set_result(FORBIDDEN,
313             "User does not have permission to access $resource ($key)");
314 0           return undef;
315             } # allowed
316              
317              
318             =head2 client_info
319              
320             Returns a hash reference with the info from the clients table.
321              
322             PARAMETERS:
323              
324             CLIENT_ID: The database ID of the client which is seeking to connect to
325             CAS.
326              
327             CLIENT_NAME: The name of the client which is seeking to connect to
328             CAS.
329              
330             CLIENT_DOMAIN: The domain of the client which is seeking to connect to
331             CAS.
332              
333             You can use any one. If more than one are defined, the first found in the
334             order above is used.
335              
336             client lookup on domain from SQCAS authorization
337             my $client = 0;
338             if ($HR_params->{CLIENT} =~ /^\d+$/) { $client = $HR_params->{CLIENT} }
339             else {
340             my $Qdomain = $self->{DBH}->quote($HR_params->{CLIENT});
341             $client = $self->{DBH}->selectrow_array("SELECT ID FROM Clients
342             WHERE Domain = $Qdomain");
343             error("Problem fetching client ID with $Qdomain: "
344             . $self->{DBH}->error) if $self->{DBH}->error;
345            
346             unless ($client) {
347             $self->_set_result(ERROR,"No client info provided.");
348             return undef;
349             } # client required
350             } # else look for domain in DB
351            
352              
353             =cut
354             sub client_info {
355 0     0 1   my $self = shift;
356 0 0         $self->error("Not a method call ($self)") unless blessed($self);
357 0 0         $self->_clear_result unless __PACKAGE__ eq caller;
358            
359 0           my $HR_params = shift;
360 0 0         $self->error("Parameters not passed as a hashref")
361             unless ref($HR_params) eq 'HASH';
362 0   0       my $debug = $HR_params->{DEBUG} || $self->{debug} || 0;
363 0           my $dbh = $self->{dbh};
364            
365 0           my $where = 'BROKEN';
366 0 0         if (defined $HR_params->{CLIENT_ID}) {
    0          
    0          
367 0           $where = "WHERE ID = $HR_params->{CLIENT_ID}";
368             } # if ID provided
369             elsif ($HR_params->{CLIENT_NAME}) {
370 0           my $Qname = $dbh->quote($HR_params->{CLIENT_NAME});
371 0           $where = "WHERE Name = $Qname";
372             } # if name provided
373             elsif ($HR_params->{CLIENT_DOMAIN}) {
374 0           my $Qdom = $dbh->quote($HR_params->{CLIENT_DOMAIN});
375 0           $where = "WHERE Domain = $Qdom";
376             } # if domain provided
377             else {
378 0           $self->_set_result(BAD_REQUEST, "No client identification provided.");
379 0           return undef;
380             } # else
381            
382 0   0       my $HR_clients = $dbh->selectrow_hashref("SELECT * FROM Clients
383             $where") || '';
384 0 0         if ($DBI::err) {
385 0           $self->_set_result(ERROR,
386             "Problem geting client data: $DBI::errstr");
387 0           return undef;
388             } # if dbi error
389            
390 0 0         $self->gripe(Dumper($HR_clients)) if $debug > 1;
391            
392 0           $self->_set_result(OK, "Returning hash of client data");
393 0           return $HR_clients;
394             } # client_info
395              
396              
397             =head2 enum_to_array
398              
399             Sole argument is the 'DESC ' to be used. Sets error
400             if not an enum field. Returns a list of the possible enum (or set) values.
401              
402             =cut
403             sub enum_to_array {
404 0     0 1   my $self = shift;
405 0 0         $self->error("Not a method call") unless blessed($self);
406 0 0         $self->_clear_result unless __PACKAGE__ eq caller;
407 0 0         my $desc_stmnt = shift or $self->error("DESC statement required");
408            
409 0   0       my $debug = $self->debug || 0;
410 0           my $dbh = $self->{dbh};
411            
412 0 0         unless ($desc_stmnt =~ /^DESC [\w\.]+ \w+$/i) {
413 0           $self->_set_result(BAD_REQUEST,
414             "Description statement ($desc_stmnt) does not look correct");
415 0           return undef;
416             } # be strict about DB call
417            
418 0           my ($field,$enum) = $dbh->selectrow_array($desc_stmnt);
419 0 0         if ($DBI::err) {
420 0           $self->_set_result(ERROR,
421             "Problem getting description of field from '$desc_stmnt: "
422             . $DBI::errstr);
423 0           return undef;
424             } # SQL problem
425 0 0         unless ($enum =~ /^enum|^set/i) {
426 0           $self->_set_result(ERROR, "Feild described does not appear to be "
427             . "enum or set. Type = $enum.");
428 0           return undef;
429             } # not parsable as enum
430            
431 0           (my $vals) = $enum =~ /\((.+)\)/;
432 0           $vals =~ s/^'//;
433 0           $vals =~ s/'$//;
434 0           my @enums = split(/','/,$vals);
435 0 0         unless (@enums) {
436 0           $self->_set_result(ERROR, "No values found from $desc_stmnt.");
437 0           return undef;
438             } # if no values found
439            
440 0           $self->_set_result(OK, "Returning list of possible values");
441 0           return @enums;
442             } # enum_to_array
443              
444              
445             # If it gets to AUTOLOAD, we'll assume it's a DBI method and hand it off
446             sub AUTOLOAD {
447 0     0     my $self = shift;
448            
449 0 0         $self->error("Not a method call") unless blessed($self);
450 0 0         $self->_clear_result unless __PACKAGE__ eq caller;
451            
452 0           my $method = $AUTOLOAD;
453 0           $method =~ s/.*:://;
454            
455             # confess("What is going on with $method!!!");
456            
457 0 0         unless ($self->{dbh}->can($method)) {
458 0           $self->error("DBI/DBD::mysql do not appear to support $method");
459             } # unless call is something DBI does
460            
461             # result code ACCEPTED should only be set here in this module
462 0           $self->_set_result(ACCEPTED,
463             "Handing request off to DBI - CAS::DB is done");
464 0           return $self->{dbh}->$method(@_);
465             } # AUTOLOAD
466              
467              
468             # allow calls to $self->err and $self->errstr to mimic the use of the DBI vars
469             # these are designed for external use only!!!
470             sub err {
471 0     0 0   my $self = shift;
472 0 0         $self->error("Not a method call") unless blessed($self);
473            
474             # if response code is ACCEPTED then the last thingthis object did
475             # should have been an AUTOLOAD call directly to DBI
476 0 0         return $DBI::err if $self->response_is(ACCEPTED);
477            
478             # otherwise the only code that should be acceptible once a call is finished
479             # is OK
480 0 0         return 1 unless $self->response_is(OK);
481            
482             # if not a DBI call and code is OK, there was (we hope) no error
483 0           return 0;
484             } # err
485              
486             sub errstr {
487 0     0 1   my $self = shift;
488 0 0         $self->error("Not a method call") unless blessed($self);
489            
490             # if response code is ACCEPTED then the last thing this object did
491             # should have been an AUTOLOAD call directly to DBI
492 0 0         return $DBI::errstr if $self->response_is(ACCEPTED);
493            
494             # if the response code is OK, there is no 'errstr' - the caller can use
495             # messages to see all messages generated during last method call
496 0 0         return '' if $self->response_is(OK);
497            
498             # if not a DBI call and code is not OK, there was (we hope) no error
499 0 0         return wantarray ? ($self->messages) : $self->messages;
500             } # errstr
501              
502              
503             # this really neads to be called explicitly from a child handler under mod_perl
504             sub DESTROY {
505 0     0     my $self = shift;
506            
507 0           my $dbh = $self->{dbh};
508            
509 0 0 0       if ($dbh && $dbh->ping) {
510 0           $dbh->do("UNLOCK TABLES");
511 0           $dbh->disconnect;
512             } # if we have a database handle
513            
514             } # object cleanup
515              
516             =head1 AUTHOR
517              
518             Sean P. Quinlan, C<< >>
519              
520             =head1 BUGS
521              
522             Please report any bugs or feature requests to
523             C, or through the web interface at
524             L.
525             I will be notified, and then you'll automatically be notified of progress on
526             your bug as I make changes.
527              
528             =head1 SUPPORT
529              
530             You can find documentation for this module with the perldoc command.
531              
532             perldoc CAS
533              
534              
535             The home page for this project is perl-cas.org.
536              
537             The mailing list for Perl CAS can be found at:
538             http://mail.perl-cas.org/mailman/listinfo/developers_perl-cas.org
539              
540             You can also look for information at:
541              
542             =over 4
543              
544             =item * AnnoCPAN: Annotated CPAN documentation
545              
546             L
547              
548             =item * CPAN Ratings
549              
550             L
551              
552             =item * RT: CPAN's request tracker
553              
554             L
555              
556             =item * Search CPAN
557              
558             L
559              
560             =back
561              
562             =head1 ACKNOWLEDGEMENTS
563              
564             =head1 COPYRIGHT & LICENSE
565              
566             Copyright 2006 Sean P. Quinlan, all rights reserved.
567              
568             This program is free software; you can redistribute it and/or modify it
569             under the same terms as Perl itself.
570              
571             =cut
572              
573             1; # End of CAS::DB