diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index 0271fffd7..14ffcc6e3 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/Documentation/SOGo Mozilla Thunderbird Configuration.odt b/Documentation/SOGo Mozilla Thunderbird Configuration.odt index 5b1f09394..903ecc50c 100644 Binary files a/Documentation/SOGo Mozilla Thunderbird Configuration.odt and b/Documentation/SOGo Mozilla Thunderbird Configuration.odt differ diff --git a/Migration/Oracle/README b/Migration/Oracle/README new file mode 100644 index 000000000..b57ed4a36 --- /dev/null +++ b/Migration/Oracle/README @@ -0,0 +1,28 @@ +Depending on your location, the timezone will have to be adjusted +in the oracleimport script. Search for "$timezone" and adjust it together +with the DTSTART/DTEND values. + +Also, if you do not want the script to send invitation requests / updates +for virtually every PUT operation, you MUST add the SCHEDULE-AGENT parameter +(with a value of NONE or CLIENT) to the ORGANIZER of the events you're about +to import. See http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-08#section-10.1 +for all details. + +Then, you need to export the data from Oracle Calendar. To do this, use +the following tools based on the type of data you want to export. + +- Calendars - + +% uniical -export -host -n -start // -end // -u -f + +- Tasks - + +% unicpoutu -host -n -start -end -u + +- Rights - + +% uniaccessrights -ls -grantor -host -n -p XXXX -grantee "S=*" -designate + +Please look at http://download.oracle.com/docs/cd/B25553_01/calendar.1012/b25486/utilities.htm for +details on those tools. + diff --git a/Migration/Oracle/oracleimport.pl b/Migration/Oracle/oracleimport.pl new file mode 100755 index 000000000..e6945dd32 --- /dev/null +++ b/Migration/Oracle/oracleimport.pl @@ -0,0 +1,887 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Time::Local; +use Time::localtime; +use Getopt::Long qw(:config bundling); +use HTTP::Request; +use LWP::UserAgent; +use Math::BigInt; +use Digest::MD5 qw(md5_hex); +use MIME::QuotedPrint; +use MIME::Base64; +use Net::LDAP; +use Time::HiRes qw (gettimeofday tv_interval); + +use constant { + QUOTED_PRINTABLE => 'auto', + # 0 : never decode data + # 1 : always decode data + # auto : try to guess from the file headers + + OWNER => undef, + # undef : use event organizer as the owner + # : force owner to be username + + DUPLICATES => 'update', + # create : create a new entry if the UID already exists + # ignore : don't create the event if the UID already exists + # update : update the entry with the same UID (if deleted, stays deleted) + # replace : if the entry exists and was deleted, resurect it + RECURRENT => 0, + # smart : group new events with same UID as one recurrent event + + LDAP_HOST => 'ldap://ldap.foobar.edu', + LDAP_BIND_DN => 'uid=sogo,ou=applications,dc=foobar,dc=edu', + LDAP_BIND_PW => 'PASSWORD', + LDAP_BASE => 'ou=people,dc=foobar,dc=edu', + LDAP_USERNAME => 'uid', + LDAP_EMAIL => 'mail', + LDAP_EMAIL_FILTER => '(|(mail=%s)(mailAlternateAddress=%s))', + + FORCE_USERNAME => undef, + FORCE_CLOSE => 0, + DRYRUN => 0, + DEBUG => 1 +}; + +$| = 1; + +# Global variables +my $file; +my ($username, $email); +my $url; +my ($host, $port, $authusername, $password); +my $ua; +my $ldap; +my %duplicatedUID = (); +my $pwdhash; + +my $timezone = <<_EOF; + +BEGIN:VTIMEZONE +TZID:/inverse.ca/20091015_1/America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +_EOF + +sub usage +{ + my $msg = shift; + + print "$msg\n" if ($msg); + print "Usage: $0 \n"; + print " must have the form http[s]://[superuser]:[password]\@hostname\n"; + print " The full URL will be build using the username.\n"; + print " must be 'events', 'tasks' or 'rights' to specify the type of data\n"; + print " must have the form .\n"; + print "\n"; +} + +sub getEmailByUsername +{ + my ($ldap, $username) = @_; + + my $results = $ldap->search(base => LDAP_BASE, + filter => '('.LDAP_USERNAME.'='.$username.')', + attrs => [(LDAP_EMAIL)]); + if ($results->count != 1) { + print "Unexpected number of LDAP entries (",$results->count,") for $username\n"; + return 0; + } + my $entry = $results->entry(0); + + return $entry->get_value(LDAP_EMAIL); +} + +my %emailToUserName; + +sub getUsernameByEmail +{ + my ($ldap, $email) = @_; + + if (!defined($emailToUserName{$email})) { + my $results = $ldap->search(base => LDAP_BASE, + filter => sprintf(LDAP_EMAIL_FILTER, $email, $email), + attrs => [(LDAP_USERNAME)]); + if ($results->count != 1) { + print "Unexpected number of entries return for $email\n"; + return 0; + } + my $entry = $results->entry(0); + $emailToUserName{$email} = $entry->get_value(LDAP_USERNAME); + } + + return $emailToUserName{$email}; +} + +sub calendarUrl +{ + my $username = $_[0]; + my $uid = $_[1] || ""; + + return "$url/SOGo/dav/$username/Calendar/personal/$uid"; +} + +sub httpRequest +{ + my ($request, $uid) = @_; + + my $result = 1; + my $i; + for ($i = 0; $i < 30; $i++) { + my $response = $ua->request($request); + if ($response->is_success) { + print $request->method, " $uid:\t", $response->status_line, "\n"; + last; + } + else { + print STDERR "ERR ", $request->method, " $uid:\t", $response->status_line, "\n"; + if ($response->code == 500) { + print STDERR "INFO sleeping 2 secs\n"; + sleep(2); + } + else { + $result = 0; + last; + } + } + } + + if ($i == 30) { + print STDERR "ERR ", $request->method, " $uid:\tCan't reach server for the past 60 secs - exiting.\n"; + exit(-4); + } + + return $result; +} + +sub userCalendarExists +{ + my ($username) = @_; + my $result = 0; + + my $propfind = ''; + my $request = HTTP::Request->new(); + $request->method('PROPFIND'); + $request->uri(&calendarUrl($username)); + $request->header('Content-Type' => 'text/xml; charset=utf8'); + $request->header('Content-Length' => length($propfind)); + $request->header('Depth' => 0); + $request->header('Authorization' => "Basic $pwdhash"); + $request->content($propfind); + + $result = &httpRequest($request, $username); + + return $result; +} + +sub searchByUid +{ + my ($username, $uid) = @_; + my $result = 0; + + my $request = HTTP::Request->new(); + $request->method('GET'); + $request->uri(&calendarUrl($username, $uid)); + + $result = &httpRequest($request, $uid); + + return $result; +} + +sub deleteEvent() +{ + my ($username, $uid) = @_; + my $result = 0; + + return $result if (DRYRUN); + + my $request = HTTP::Request->new(); + $request->method('DELETE'); + $request->uri(&calendarUrl($username, $uid)); + + $result = &httpRequest($request, $uid); + + return $result; +} + +sub putEvent(\%) +{ + my (%vevent) = %{(shift)}; + my $count = shift; + + my $uid = $vevent{'uid'}; + + # decode data + $vevent{'data'} =~ s/\r//g; + $vevent{'data'} =~ s/([^=])\n /$1/g; + if (QUOTED_PRINTABLE eq '1' || + (QUOTED_PRINTABLE eq 'auto' && $vevent{'encoding'} && $vevent{'encoding'} =~ m/quoted-printable/)) { + $vevent{'data'} = decode_qp($vevent{'data'}); + } + + # for "notes", we need to add one day to the DTEND + my $oracleEventType; + if ($vevent{'data'} =~ /X-ORACLE-EVENTTYPE:(.*)/) { + $oracleEventType = $1; + } else { + $oracleEventType = "unknown"; + } + if ($oracleEventType eq 'DAILY NOTE') { +# if ($vevent{'data'} =~ /DTEND;VALUE=DATE:(\d{4})(\d{2})(\d{2})/) { +# my ($mday,$mon,$year) = ($3, $2, $1); +# my $seconds = timelocal(0, 0, 0, $mday, $mon - 1, $year - 1900); +# $seconds += 86400; +# # we specify "CORE::" because we expect an array instead of a +# # magical hash +# my @newLocalTime = CORE::localtime($seconds); +# $mday = $newLocalTime[3]; +# $mon = $newLocalTime[4] + 1; +# $year = $newLocalTime[5] + 1900; +# my $newEndDate = sprintf("%.4d%.2d%.2d", $year, $mon, $mday); +# my $dtEndPrefix = "DTEND;VALUE=DATE:"; +# my $dtEndIndex = index $vevent{'data'}, $dtEndPrefix; +# if ($dtEndIndex > -1) { +# my $partLength = $dtEndIndex + length($dtEndPrefix); +# $vevent{'data'} = sprintf("%s%s%s", +# substr($vevent{'data'}, 0, $partLength), +# $newEndDate, +# substr($vevent{'data'}, $partLength + 8)); +# } +# } + + # we set a timezone for dates in all day events to ensure that SOGo + # does not put them in UTC + $vevent{'data'} =~ s@BEGIN:VEVENT@${timezone}BEGIN:VEVENT@; + $vevent{'data'} =~ s@DTSTART;VALUE=DATE:@DTSTART;VALUE=DATE;TZID=/inverse.ca/20091015_1/America/New_York:@; + $vevent{'data'} =~ s@DTEND;VALUE=DATE:@DTEND;VALUE=DATE;TZID=/inverse.ca/20091015_1/America/New_York:@; + } + + # parse attendees + my $hasAttendees = 0; + while ($vevent{'data'} =~ m/ATTENDEE;(.+)$/gm) { + my @parameters = split(';', $1); + $vevent{'attendees'} = [] unless ($vevent{'attendees'}); + my %attendee = (); + foreach (@parameters) { + #print $_,"\n"; + if (m/^(\S+)=(.+)$/) { + print "\t$1 => $2\n"; + $attendee{$1} = $2; + if ($1 eq 'CN' && $2 =~ m/mailto:(\S+)$/) { + $attendee{'CN'} = $1; + $attendee{'username'} = &getUsernameByEmail($ldap, $1); + $hasAttendees = 1 if ($1 ne $email); # Attendee is not the owner + } + } + } + push(@{$vevent{'attendees'}}, \%attendee); + } + + # handle duplicated UID within file + if ($duplicatedUID{$uid}) { + $uid .= $duplicatedUID{$uid}; + $duplicatedUID{$vevent{'uid'}}++; + } + else { + $duplicatedUID{$uid} = 1; + } + + unless (DUPLICATES eq 'update') { + if (&searchByUid($username, $uid)) { + print STDERR "Event with UID '$uid' already exists\n"; + return 0 if (DUPLICATES eq 'ignore'); + + if (DUPLICATES eq 'replace') { + &deleteEvent($username, $uid); + } +# elsif ($hasAttendees) { +# print STDERR "UID collision (",$uid,") for an event with attendee(s); ignoring it\n"; +# return 0; +# } + else { + # Make sure UID is unique (DUPLICATES eq 'create') + my $i = ($duplicatedUID{$vevent{'uid'}})?$duplicatedUID{$vevent{'uid'}}:1; + for ($uid .= $i; + &searchByUid($username, $uid) == 1; + print STDERR "Event with UID '$uid' already exists\n", + $uid = $vevent{'uid'} . $i, + $i++) + {}; + $duplicatedUID{$vevent{'uid'}} = $i + 1; + } + } + } + + # If UID already exists, change it in the VEVENT + if ($uid ne $vevent{'uid'}) { + $vevent{'data'} =~ s/^UID:\S+$/UID:$uid/m; + } + + if ($vevent{'data'} =~ m/^SUMMARY:[;\s]*$/m) { + $vevent{'data'} =~ s#^(BEGIN:VEVENT)#$1\nSUMMARY: (untitled event)#m; + } + + if ($vevent{'recurrent'}) { + $vevent{'data'} =~ s#^(BEGIN:VEVENT)#$1\nRRULE:FREQ=DAILY;COUNT=1;INTERVAL=1#m; + } + + $vevent{'data'} = + "BEGIN:VCALENDAR\n" . + "VERSION:2.0\n" . + "PRODID:Oracle/Oracle Calendar Server 10.1.2.3.3\n" . + $vevent{'data'} . + "END:VCALENDAR"; + + if (DEBUG) { + foreach my $key (keys %vevent) { + if (ref($vevent{$key}) eq 'ARRAY') { + print "$key =>\n"; + foreach (@{$vevent{$key}}) { + my %hash = %{$_}; + print " =>"; + foreach (keys %hash) { + print "\t$_ => $hash{$_}\n"; + } + } + } + else { + print "$key = \n\t", $vevent{$key},"\n";# unless ($key eq 'data'); + } + } + } + print "PUT ",&calendarUrl($username, $uid),"\n"; + + return 0 if (DRYRUN); + + my $request = HTTP::Request->new(); + + $request->method('PUT'); + $request->uri(&calendarUrl($username, $uid)); + + $request->header('Authorization' => "Basic $pwdhash"); + + #$request->header('Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'); + #$request->header('Accept-Language' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'); + #$request->header('Content-Type' => 'text/plain; charset=utf-8'); + $request->header('Content-Type' => 'text/calendar; charset=utf-8'); + $request->header('Content-Length' => length($vevent{'data'})); + $request->header('x-sogo-mode' => 'M'); + #$request->header('Connection' => 'TE'); + if (FORCE_CLOSE && ($count % FORCE_CLOSE) == 0) { + print "Force connection close (no keepalive)\n"; + $request->header('Connection' => 'close'); + } + #$request->header('TE' => 'trailers'); + #$request->header('Depth' => 1); + #$request->header('Accept-Charset' => 'utf-8'); + #$request->header('Accept' => 'text/plain'); + $request->content($vevent{'data'}); + + + return &httpRequest($request, $uid); +# my $i; +# for ($i = 0; $i < 30; $i++) { +# my $response = $ua->request($request); +# if ($response->is_success) { +# print "PUT $uid:\t", $response->status_line, "\n"; +# last; +# } +# else { +# print STDERR "ERR PUT $uid:\t", $response->status_line, "\n"; +# sleep(2); +# } +# } +# +# if ($i == 30) { +# print STDERR "ERR PUT $uid:\tCan't reach server for the past 60 secs - exiting.\n"; +# exit(-4); +# } +} + +sub parseEventsFile +{ + my $file = shift; + + my %vevent = (); + my %last_vevent = (); +# data +# uid +# encoding +# organizer +# username +# recurrent + + my $count = 0; + my $count_created = 0; + my $bytes_count = 0; + my $elapsed_time = [gettimeofday]; + + while (my $line = ) { + $line =~ s/\r$//; # remove dos linebreaks + if ($line =~ m/^BEGIN:VEVENT$/) { + $vevent{'data'} = $line; + } + elsif ($line =~ m/^END:VEVENT$/) { + $vevent{'data'} .= $line; + #if ($vevent{'organizer'} eq $email) { + $count++; + $bytes_count += length($vevent{'data'}); + + if (RECURRENT eq 'smart') { + if (%last_vevent) { + if ($last_vevent{'uid'} eq $vevent{'uid'}) { + $last_vevent{'data'} .= $vevent{'data'}; + $last_vevent{'recurrent'} = 1; + if ($last_vevent{'username'} ne $vevent{'username'}) { + print "ERR: Matching UID with different organizers!\n"; + } + } + else { + $count_created += &putEvent(\%last_vevent, $count); + %last_vevent = %vevent; + } + } + else { + %last_vevent = %vevent; + } + } +# elsif ($vevent{'rdate'}) { +# # Ignore event with RDATE attributes -- they are not currently +# # supported in SOGo (web) +# $vevent{'rdate'} = undef; +# print "Event with RDATE -- ignored\n"; +# } + else { + $count_created += &putEvent(\%vevent, $count); + } + #$last_data = $vevent{'data'}; + #last; + #} + #else { + #print $vevent{'uid'},": $email ($username) NOT organizer ",$vevent{'organizer'}," (",$vevent{'username'},"); verify event\n"; + #} + $vevent{'data'} = undef; + #last; + } + elsif ($vevent{'data'}) { + if ($line !~ m/^$/ + && $line !~ m/^RECURRENCE-ID/ + && $line !~ m/^RDATE:/) { + if ($line =~ m/UID:\s*(\S+)$/) { + $vevent{'uid'} = $1; + $vevent{'uid'} =~ s/[#&\/]/-/g; + $vevent{'uid'} =~ s/\.//g; + $line =~ s/^(UID:).*$/$1$vevent{'uid'}/; + } + elsif ($line =~ m/^ORGANIZER:(?:mailto:)?(\S+)$/) { + $vevent{'organizer'} = $1; + $vevent{'username'} = &getUsernameByEmail($ldap, $1); + } +# elsif ($line =~ m/^RDATE:/) { +# $vevent{'rdate'} = 1; +# } + $vevent{'data'} .= $line unless (); + } + else { + print "ignored: '$line'\n"; + } + } + elsif ($line =~ m/Content-Transfer-Encoding: (\S+)$/) { + $vevent{'encoding'} = $1; + } + } + + if (%last_vevent) { + $count_created += &putEvent(\%last_vevent, $count); + } + + printf "\nParsed %i events, %i new: %.1f KB in %.1f seconds\n", + $count, $count_created, ($bytes_count/1024), tv_interval($elapsed_time); + + return 1; +} + +sub gmtTime { + my $time = localtime(shift); + #my ($second,$minute,$hour,$dayofmonth,$month,$year,$weekday,$dayofyear,$isdst) = localtime($time); + + #$year += 1900; + #$month++; + #$hour -= $isdst; + + return sprintf("%04d%02d%02dT%02d%02d%02dZ", + $time->year+1900, + $time->mon+1, + $time->mday, + $time->hour-$time->isdst, + $time->min, + $time->isdst); +} + +sub putTask(\%) { + my (%task) = %{(shift)}; + my $count = shift; + my $bytes_count_ref = shift; + + return 0 unless ($task{'summary'}); + + my $now = &gmtTime(time); + my $uid = md5_hex(%task); + my $data = <<'VCAL'; +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Inverse inc.//SOGo 0.9//EN +BEGIN:VTODO +UID:%s +SUMMARY:%s +LOCATION: +VCAL + + $data = sprintf($data, uc($uid), $task{'summary'}); + $data .= "PRIORITY:" . $task{'priority'} . "\n" if ($task{'priority'}); + $data .= "CREATED:$now\n"; + $data .= "DTSTAMP:$now\n"; + $data .= "LAST-MODIFIED:$now\n"; + $data .= "DTSTART:" . &gmtTime($task{'start'}) . "\n" if ($task{'start'}); + $data .= "DUE:" . &gmtTime($task{'end'}) . "\n" if ($task{'end'}); + if (defined($task{'completion'})) { + if (scalar($task{'completion'}) < 100) { + $data .= "STATUS:IN-PROCESS\n"; + } + else { + $data .= "STATUS:COMPLETED\n"; + } + $data .= "PERCENT-COMPLETE:" . $task{'completion'} . "\n"; + } + $data .= "DESCRIPTION:" . join("\\r\\n", @{$task{'description'}}) . "\n" if ($task{'description'}); + $data .= "END:VTODO\n"; + $data .= "END:VCALENDAR\n"; + + $$bytes_count_ref += length($data); + + print $data if (DEBUG); + print "PUT ",&calendarUrl($username, $uid),"\n"; + + return 0 if (DRYRUN); + + my $request = HTTP::Request->new(); + + $request->method('PUT'); + $request->uri(&calendarUrl($username, $uid)); + $request->header('Authorization' => "Basic $pwdhash"); + $request->header('Content-Type' => 'text/calendar; charset=utf-8'); + $request->header('Content-Length' => length($data)); + $request->header('x-sogo-mode' => 'M'); + if (FORCE_CLOSE && ($count % FORCE_CLOSE) == 0) { + print "Force connection close (no keepalive)\n"; + $request->header('Connection' => 'close'); + } + $request->content($data); + + return &httpRequest($request, $uid); +} + +sub parseTasksFile { +# S 9265740 +# D 9266220 +# T task august 13th +# R 1 +# L 100 +# M bar foo +# W bar foo +# C task august 13th 2008 +# C line 2 description +# C line 3 +# O + +# BEGIN:VCALENDAR +# VERSION:2.0 +# PRODID:-//Inverse inc.//SOGo 0.9//EN +# BEGIN:VTODO +# UID:26A-4979F880-1-B72F03D0 +# SUMMARY:this is a task +# LOCATION:there +# PRIORITY:1 +# STATUS:IN-PROCESS +# CREATED:20090123T170443Z +# DTSTAMP:20090123T170443Z +# LAST-MODIFIED:20090123T170443Z +# DTSTART:20090123T171500Z +# DUE:20090124T181500Z +# PERCENT-COMPLETE:40 +# DESCRIPTION:foo +# END:VTODO +# END:VCALENDAR + + #my $file = $_[0]; + my $count = 0; + my $count_created = 0; + my $bytes_count = 0; + my $elapsed_time = [gettimeofday]; + my %task = (); + # Start and due times are computed in minutes since since Jan 1 1991 + my $basetime = timelocal(0, 0, 0, 1, 0, 91); +# my $tm = localtime($basetime); +# printf("Base date: %04d/%02d/%02d %02d:%02d:%02d\n", +# $tm->year+1900, $tm->mon+1, $tm->mday, +# $tm->hour, $tm->min, $tm->sec); + +# open (my $tasksfile, $file) +# or die "Cannot open tasks file '$file'"; + +# while ($line = <$tasksfile>) { + while (my $line = ) { + #$line =~ s/\n$//; + chomp $line; + if ($line =~ m/^O/) { + if (%task) { + $count++; + $count_created += &putTask(\%task, $count, \$bytes_count); + %task = (); + } + } + elsif ($line =~ m/^T (.+)/) { + $task{'summary'} = $1; + } + elsif ($line =~ m/^S (\d+)/ && $1) { + $task{'start'} = $1*60 + $basetime; + } + elsif ($line =~ m/^D (\d+)/ && $1) { + # End time (number of minutes since Jan 1 1991) + $task{'end'} = $1*60 + $basetime; +# $tm = localtime($task{'end'}); +# printf("End date: %04d/%02d/%02d %02d:%02d:%02d\n", +# $tm->year+1900, $tm->mon+1, $tm->mday, +# $tm->hour, $tm->min, $tm->sec); + } + elsif ($line =~ m/^R (\w+)/) { + $task{'priority'} = $1; + } + elsif ($line =~ m/^L (\w+)/) { + $task{'completion'} = $1; + } + elsif ($line =~ m/^C (.+)/) { + $task{'description'} = () unless ($task{'description'}); + push(@{$task{'description'}}, $1); + } + } + + #close ($tasksfile); + close (CAL); + + printf "\nParsed %i tasks, %i new: %.1f KB in %.1f seconds\n", + $count, $count_created, ($bytes_count/1024), tv_interval($elapsed_time); + + return 1; +} + +sub parseRightsFile +{ + my $file = $_[0]; + + open (my $rightsfile, $file) + or die "Cannot open rights file '$file'"; + + my $line = <$rightsfile>; + $line =~ s/\n$//; +#line:---procuration, username, foo.bar@foo.edu + my $user; + if ($line =~ m@^\-\-\-procuration, ([^,]+),@) { + $user = $1; + print "rights for user's calendar: $user\n"; + } + else { + die "Could not parse procuration line: $line"; + } + + my $next = 0; # 0 = Grantee, 1 = Designate right + my $grantee; + my $rights; + + while ($line = <$rightsfile>) { + $line =~ s/\n$//; + if ($next == 0) { + if ($line =~ m@^Grantee:\ S=[^/]+/G=[^/]+/UID=([^/]+)/ID=[^/]+/NODE\-ID=[^/]+$@) { + $grantee = $1; + } + else { + die "Expected or mal-formed 'Grantee' line: $line"; + } + $next = 1; + } + elsif ($next == 1) { + if ($line =~ m@^Designate\ Right:\ (.*)$@) { + my $oracleRights = $1; + $rights = &convertOracleRights($oracleRights); + } + else { + die "Expected or mal-formed 'Designate Right' line: $line"; + } + &grantUserRights($grantee, $rights, $user); + $next = 0; + } + } + + close ($rightsfile); +} + +#line:Designate Right: CONFIDENTIALEVENT=NONE/CONFIDENTIALTASK=NONE/NORMALEVENT=MODIFY/NORMALTASK=MODIFY/PERSONALEVENT=VIEWTIME/PERSONALTASK=NONE/PUBLICEVENT=MODIFY/PUBLICTASK=MODIF +sub convertOracleRights() +{ + my $oracleRights = $_[0]; + my %keyMapping = ( 'CONFIDENTIAL' => 'Confidential', + 'NORMAL' => 'Public', + 'PUBLIC' => 'Public', + 'PERSONAL' => 'Private' ); + my %valueMapping = ( 'VIEW' => 'Viewer', # à confirmer + 'VIEWTIME' => 'DAndTViewer', + 'MODIFY' => 'Modifier', + 'REPLY' => 'Responder' ); + + my %rights = (); + + my @parsedRights = split('/', $oracleRights); + foreach my $parsedRight (@parsedRights) { + my ($key, $value) = split('=', $parsedRight); + if ($key =~ /(.*)EVENT$/ && $value ne 'NONE') { + $key = $1; + die "No mapping found for key '$key'" + unless defined $keyMapping{$key}; + die "No mapping found for value '$value'" + unless defined $valueMapping{$value}; + $rights{$keyMapping{$key}.$valueMapping{$value}} = 1; + } + } + + return [keys %rights]; +} + +sub grantUserRights() +{ + my ($grantee, $rights, $user) = @_; + + die "No grantee specified" + unless defined $grantee; + die "No rights specified" + unless defined $rights; + die "No user specified" + unless defined $user; + + my $xmlRights = ""; + + foreach my $right (@$rights) { + $xmlRights .= "<$right/>"; + } + my $content = ( '' . "\n" + . '' . $xmlRights . '' ); + + my $request = HTTP::Request->new(); + $request->method('POST'); + $request->uri(&calendarUrl($user)); + $request->header('Authorization' => "Basic $pwdhash"); + $request->header('Content-Type' => 'application/xml'); + $request->header('Content-Length' => length($content)); + $request->content($content); + + my $result = &httpRequest($request, $username); + + my $response = $ua->request($request); +} + +## +## MAIN +## + +if ($#ARGV < 3) { + &usage(); + exit(-1); +} + +$url = $ARGV[0]; +$username = $ARGV[1]; +my $type = $ARGV[2]; +$file = $ARGV[3]; + +if ($type ne 'events' && $type ne 'tasks' && $type ne 'rights') { + usage("The argument 'type' does not have a proper value: '$type'"); + exit(-1); +} + +# Prepare LDAP connection +$ldap = new Net::LDAP(LDAP_HOST) or die "Can't connect to LDAP server: $@.\n"; +my $msg = $ldap->bind(LDAP_BIND_DN, password => LDAP_BIND_PW); +if ($msg->is_error()) { + die "Can't bind to LDAP server: ".$msg->error()."\n"; +} + +# Verify file name format; extract username +#if ($file =~ m/^(?!.+?\W)?events\.(\d{8}|invite\d+)$/) { +#if ($file =~ m/^(?:.+\/)?(events|tasks|rights)\.(\d{8}|invite\d+|[a-z]+)(\.test\d?)?$/) { +# $username = $2; +$email = &getEmailByUsername($ldap, $username); +print "$username = $email\n"; + +if (FORCE_USERNAME) { + $username = FORCE_USERNAME; + print "Force username to $username\n"; +} + +# Open iCalendar file +open (CAL, $file) or die "Can't open file $file: $!\n"; + +# Prepare HTTP query +if ($url =~ m#^(https?://)(?:([^:]+):([^@]+)@)?([^/]+)#) { + ($authusername, $password, $host) = ($2, $3, $4); + $url = $1.$4; + if ($host =~ m/:(\d+)/) { + $port = $1; + } + elsif ($url =~ m/^https/) { + $port = '443'; + $host .= ":$port"; + } + else { + $port = '80'; + $host .= ":$port"; + } +# print "host = $host, auth = $authusername\n"; +} +else { + &usage("The URL doesn't have the proper format."); + exit(-3); +} + +$pwdhash = encode_base64($authusername . ':' . $password); + +$ua = LWP::UserAgent->new(); +$ua->agent('Mozilla/5.0'); +$ua->timeout(1800); + +# Verify is user personal calendar exists (or can be automatically created) +die "Can't access personal calendar of username $username\n" unless (&userCalendarExists($username)); + +my $parsers = { 'events' => \&parseEventsFile, + 'tasks' => \&parseTasksFile, + 'rights' => \&parseRightsFile }; +$parsers->{$type}($file); + +exit; diff --git a/NEWS b/NEWS index 0d7412b5b..389096e1a 100644 --- a/NEWS +++ b/NEWS @@ -1,10 +1,12 @@ -1.3-201008xx (1.3.1) +1.3-20100819 (1.3.1) -------------------- - added migration scripts for Horde (email signatures and address books) +- added migration script for Oracle Calendar (events, tasks and access rights) - added Polish translation - added crypt support to SQL sources - updated Ukrainian translation - added the caldav-auto-schedule capability +- improved support for IE8 1.3-20100721 (1.3.0) -------------------- diff --git a/Tests/Integration/README b/Tests/Integration/README index 5cab950d4..ca98c90c0 100644 --- a/Tests/Integration/README +++ b/Tests/Integration/README @@ -6,7 +6,9 @@ setup 1) copy config.py.in to config.py (make sure to never EVER add it to monotone) 2) edit config.py to suit your environment -3) run the test scripts +3) make sure that you use a fresh database, with no prior information in it +4) make sure that SOGoCalendarDefaultRoles and SOGoContactsDefaultRoles are empty or undefined +5) run the test scripts runnable scripts ---------------- diff --git a/UI/MainUI/Version b/UI/MainUI/Version index 4010e5dd4..458cc5adb 100644 --- a/UI/MainUI/Version +++ b/UI/MainUI/Version @@ -1,3 +1,13 @@ # Version file +<<<<<<< variant A SUBMINOR_VERSION:=1 + +# v0.9.1 requires Main v0.9.59 +>>>>>>> variant B +SUBMINOR_VERSION:=1 +####### Ancestor +SUBMINOR_VERSION:=0 + +# v0.9.1 requires Main v0.9.59 +======= end diff --git a/UI/WebServerResources/SOGoResizableTable.js b/UI/WebServerResources/SOGoResizableTable.js index 86becd48a..9bde09c52 100644 --- a/UI/WebServerResources/SOGoResizableTable.js +++ b/UI/WebServerResources/SOGoResizableTable.js @@ -24,11 +24,12 @@ var SOGoResizableTableInterface = { } SOGoResizableTable._resize(this, $(cell), i, null, cell.getWidth()); } + this.computeColumnsWidths(); Event.observe(window, "resize", this.resize.bind(this)); }, resize: function(e) { - // Only resize the columns after a certain delay, otherwise it slow + // Only resize the columns after a certain delay, otherwise it slows // down the interface. if (this.delayedResize) window.clearTimeout(this.delayedResize); this.delayedResize = this._resize.bind(this).delay(0.2); @@ -62,7 +63,7 @@ var SOGoResizableTableInterface = { this.ratios = relativeWidths; }, - saveColumnsState: function() { + computeColumnsWidths: function() { this.ratios = new Hash(); var tableWidth = 100/this.getWidth(); var cells = $(this).down('tr').childElements(); @@ -71,6 +72,10 @@ var SOGoResizableTableInterface = { if (cell.hasClassName('resizable')) this.ratios.set(cell.id, Math.round(cell.getWidth()*tableWidth)); } + }, + + saveColumnsState: function() { + this.computeColumnsWidths(); if (!$(document.body).hasClassName("popup")) { var url = ApplicationBaseURL + "saveColumnsState"; var data = this.ratios; diff --git a/UI/WebServerResources/UIxPreferences.js b/UI/WebServerResources/UIxPreferences.js index 77fed756f..51bca2fc0 100644 --- a/UI/WebServerResources/UIxPreferences.js +++ b/UI/WebServerResources/UIxPreferences.js @@ -251,7 +251,6 @@ function _editFilter(filterId) { } function onFilterAdd(event) { - log("onFilterAdd"); _editFilter("new"); event.stop(); }