diff --git a/.gitignore b/.gitignore index 92e2728c..6fbe887a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Thumbs.db *.DS_Store *~ .idea/ +vendor/ diff --git a/README.md b/README.md index cf2f8601..cf6bc3bd 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,6 @@ Anuko [Time Tracker](https://www.anuko.com/time-tracker/index.htm) is an open so * Forum: https://www.anuko.com/forum/viewforum.php?f=4 * Info for developers: https://www.anuko.com/time-tracker/info-for-developers.htm * How to contribute: https://www.anuko.com/time-tracker/contribute.htm + +## API +Created a small api to login with the username and password to get project list and to submit a timesheet. Have used a JWT authentication for login. diff --git a/WEB-INF/config.php.dist b/WEB-INF/config.php.dist old mode 100644 new mode 100755 index 9d9d90df..9279031a --- a/WEB-INF/config.php.dist +++ b/WEB-INF/config.php.dist @@ -180,3 +180,8 @@ define('AUTH_MODULE', 'db'); // A comma-separated list of default plugins for new group registrations. // Example below enables charts and attachments. // define('DEFAULT_PLUGINS', 'ch,at'); + +// configuratios for jwt authentication +define('SECRET_KEY', 'YOUR_SECRET_KEY'); +define('ISSUER_CLAIM', 'TestTimetrackerApi'); // this can be the servername +define('AUDIENCE_CLAIM', 'THE_AUDIENCE'); diff --git a/WEB-INF/lib/ttTimeHelper.class.php b/WEB-INF/lib/ttTimeHelper.class.php index 031ccfb6..6b747f21 100644 --- a/WEB-INF/lib/ttTimeHelper.class.php +++ b/WEB-INF/lib/ttTimeHelper.class.php @@ -857,7 +857,7 @@ static function getAllRecords($user_id) { return $result; } - + // getRecords - returns time records for a user for a given date. static function getRecords($date, $includeFiles = false) { global $user; diff --git a/WEB-INF/lib/ttTimeSweHelper.class.php b/WEB-INF/lib/ttTimeSweHelper.class.php new file mode 100644 index 00000000..5a5ecd19 --- /dev/null +++ b/WEB-INF/lib/ttTimeSweHelper.class.php @@ -0,0 +1,227 @@ +getUser(); + $sql = "select l.id, l.user_id, l.date, TIME_FORMAT(l.start, '%k:%i') as start, + TIME_FORMAT(sec_to_time(time_to_sec(l.start) + time_to_sec(l.duration)), '%k:%i') as finish, + TIME_FORMAT(l.duration, '%k:%i') as duration, + l.client_id, l.project_id, l.task_id, l.invoice_id, l.comment, l.billable, l.paid, l.status + from tt_log l where l.status is not NULL and l.user_id = $user_id and l.date between $from_date and $to_date order by l.id"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) + { + while ($val = $res->fetchRow()) + { + $result[] = $val; + } + } + else + { + return false; + } + return $result; + } + + static function deleteEntry($id) + { + global $user; + $mdb2 = getConnection(); + + // Delete associated files. + if ($user->isPluginEnabled('at')) + { + import('ttFileHelper'); + global $err; + $fileHelper = new ttFileHelper($err); + if (!$fileHelper->deleteEntityFiles($id, 'time')) + return false; + } + + $user_id = $user->getUser(); + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $modified_part = ', modified = now(), modified_ip = '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', modified_by = '.$user->id; + + $sql = "update tt_log set status = null".$modified_part. + " where id = $id and user_id = $user_id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if ($affected==0) + return "entry with this id doesn't exist"; + + $sql = "update tt_custom_field_log set status = null". + " where log_id = $id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + + return true; + } + + static function getprojects() + { + $result = array(); + + $mdb2 = getConnection(); + global $user; + $group_id = $user->group_id; + $org_id = $user->org_id; + $sql = "select id,name,projects as project_id,NULL as project from tt_clients as c where status is not NULL and group_id=$group_id and org_id=$org_id"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) + { + while ($val = $res->fetchRow()) + { + $result[] = $val; + } + } + else + { + return false; + } + + return $result; + } + + static function getprojectsname($id) + { + $mdb2 = getConnection(); + global $user; + $group_id = $user->group_id; + $org_id = $user->org_id; + $projects= explode(',',$id); + + foreach($projects as $pro) + { + $sql = "select id,name from tt_projects where status is not NULL and group_id=$group_id and org_id=$org_id and id=$pro group by id"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) + { + while ($val = $res->fetchRow()) + { + $result[] = $val; + } + } + else + { + return false; + } + } + return $result; + } + + static function getnullClientProjects() + { + + $mdb2 = getConnection(); + global $user; + $group_id = $user->group_id; + $org_id = $user->org_id; + + $sql = "select projects from tt_clients where group_id=$group_id and org_id=$org_id"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) + { + while ($val = $res->fetchRow()) + { + $get_projects[] = $val; + } + } + else + { + return false; + } + + $var=array(); + foreach ($get_projects as $pro) + { + $projects= explode(',',$pro[projects]); + $var=array_merge($var,$projects); + } + $var1=implode(',',$var); + $sql = 'select id,name from tt_projects where group_id=1 and org_id=1 and id not in'.'('. $var1.')'; + $res = $mdb2->query($sql); + + if (!is_a($res, 'PEAR_Error')) + { + while ($val = $res->fetchRow()) + { + $get_projects_for_null_clients[] = $val; + } + } + else + { + return false; + } + + return $get_projects_for_null_clients; + } + static function insert($fields) + { + global $user; + $mdb2 = getConnection(); + + $user_id = $user->getUser(); + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $date = $fields['date']; + $start = $fields['start']; + $finish = $fields['finish']; + + $duration = $fields['duration']; + if ($duration) { + $minutes = ttTimeHelper::postedDurationToMinutes($duration); + $duration = ttTimeHelper::minutesToDuration($minutes); + } + $client = $fields['client']; + $project = $fields['project']; + $task = $fields['task']; + $invoice = $fields['invoice']; + $note = $fields['note']; + $billable = $fields['billable']; + $paid = $fields['paid']; + + $start = ttTimeHelper::to24HourFormat($start); + if ($finish) { + $finish = ttTimeHelper::to24HourFormat($finish); + if ('00:00' == $finish) $finish = '24:00'; + } + + $created_v = ', now(), '.$mdb2->quote($_SERVER['REMOTE_ADDR']).', '.$user->id; + + if (!$billable) $billable = 0; + if (!$paid) $paid = 0; + + if ($duration) { + $sql = "insert into tt_log (user_id, group_id, org_id, date, duration, client_id, project_id, task_id, invoice_id, comment, billable, paid, created, created_ip, created_by) ". + "values ($user_id, $group_id, $org_id, ".$mdb2->quote($date).", '$duration', ".$mdb2->quote($client).", ".$mdb2->quote($project).", ".$mdb2->quote($task).", ".$mdb2->quote($invoice).", ".$mdb2->quote($note).", $billable, $paid $created_v)"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + } else { + + $duration = ttTimeHelper::toDuration($start, $finish); + if ($duration === false) $duration = 0; + if (!$duration && ttTimeHelper::getUncompleted($user_id)) return false; + + + $sql = "insert into tt_log (user_id, group_id, org_id, date, start, duration, client_id, project_id, task_id, invoice_id, comment, billable, paid, created, created_ip, created_by) ". + "values ($user_id, $group_id, $org_id, ".$mdb2->quote($date).", '$start', '$duration', ".$mdb2->quote($client).", ".$mdb2->quote($project).", ".$mdb2->quote($task).", ".$mdb2->quote($invoice).", ".$mdb2->quote($note).", $billable, $paid $created_v)"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + } + + $id = $mdb2->lastInsertID('tt_log', 'id'); + return $id; + } +} \ No newline at end of file diff --git a/api/deleteEntry.php b/api/deleteEntry.php new file mode 100644 index 00000000..ce77f15a --- /dev/null +++ b/api/deleteEntry.php @@ -0,0 +1,81 @@ +jwt; +$entry_id = $object->entry_id; +$id = $entry_id; + +$isLoggedIn = false; +//check fot token verification +if ($jwt) { + try { + error_log($jwt); + $decoded = JWT::decode($jwt, $secret_key, array('HS256')); + $isLoggedIn = true; + $userId = $decoded->data->id; + $user = new ttUser(null, $userId); + if ($isLoggedIn) { + if ($id!= "") { + //delete entry + $record = ttTimeSweHelper::deleteEntry($id); + if($record=="true"){ + $success_response = ['success' => true, 'deleted id' => $id]; + $response = json_encode($success_response); + print_r($response); + } + else { + //if entry doesnt exist + $success_response = ['success' => false, 'error' => $record]; + $response = json_encode($success_response); + print_r($response); + } + } + //if no values entered + else { + $success_response = ['error' => 'check the entered key and value']; + $response = json_encode($success_response); + print_r($response); + + } + } + } catch (Exception $e) { + $isLoggedIn = false; + http_response_code(401); + print_r(json_encode(array( + "message" => "Access denied.", + "error" => $e->getMessage() + ))); + } +} +exit(); diff --git a/api/editEntry.php b/api/editEntry.php new file mode 100644 index 00000000..abb9f947 --- /dev/null +++ b/api/editEntry.php @@ -0,0 +1,130 @@ +jwt; +$id = $object->id; +$date = $object->date; +$user_id = $object->user_id; +$client = $object->client; +$project = $object->project; +$task = $object->task; +$start = $object->start; +$finish =$object->finish; +$duration = $object->duration; + +$isLoggedIn = false; +//check fot token verification +if ($jwt) { + try { + error_log($jwt); + $decoded = JWT::decode($jwt, $secret_key, array('HS256')); + $isLoggedIn = true; + $userId = $decoded->data->id; + $user = new ttUser(null, $userId); + if ($isLoggedIn) { + + if($duration){ + //check is date is entered + if($date==""){ + $success_response = ['success' => false, 'error' => 'enter the date' ]; + $response = json_encode($success_response); + print_r($response); + } + else{ + if ($start) + { + //return false if start exist while duration is entered + $success_response = ['success' => false, 'error' => 'remove start time while duration is given' ]; + $response = json_encode($success_response); + print_r($response); + } + else{ + //update the record + $record=ttTimeHelper::update(array( + 'id' => $id, + 'date' => $date, + 'user_id' => $user_id, + 'client' => $client, + 'project' => $project, + 'task' => $task, + 'start' => $start, + 'finish' => $finish, + 'duration' => $duration + )); + } + + } + } + else{ + + if($start==""||$date==""){ + //check if start and date both exist + $success_response = ['success' => false, 'error' => 'date or start time not entered' ]; + $response = json_encode($success_response); + print_r($response); + } + else{ + $record=ttTimeHelper::update(array( + 'id' => $id, + 'date' => $date, + 'user_id' => $user_id, + 'client' => $client, + 'project' => $project, + 'task' => $task, + 'start' => $start, + 'finish' => $finish, + 'duration' => $duration + )); + } + } + if($record){ + //send true if update is done + $success_response = ['success' => $record, 'update done' => 'true' ]; + $response = json_encode($success_response); + print_r($response); + } + else + { + //send error if any false value entered + $success_response = ['success' => false, 'error' => 'wrong values entered' ]; + $response = json_encode($success_response); + print_r($response); + } + } + } catch (Exception $e) { + $isLoggedIn = false; + http_response_code(401); + print_r(json_encode(array( + "message" => "Access denied.", + "error" => $e->getMessage() + ))); + } +} +exit(); diff --git a/api/gettime.php b/api/gettime.php new file mode 100644 index 00000000..1b4f2f72 --- /dev/null +++ b/api/gettime.php @@ -0,0 +1,67 @@ +jwt; +$from_date_in= $object->from_date; +$to_date_in=$object->to_date; +$from_date =$d="'$from_date_in'"; +$to_date =$d="'$to_date_in'"; + +$isLoggedIn = false; +//check fot token verification +if ($jwt) { + try { + error_log($jwt); + $decoded = JWT::decode($jwt, $secret_key, array('HS256')); + $isLoggedIn = true; + $userId = $decoded->data->id; + $user = new ttUser(null, $userId); + if ($isLoggedIn) { + if($to_date=="''") { + $to_date = $from_date; + } + $record = ttTimeSweHelper::getAllDateRecords($from_date,$to_date ); + $success_response = ['success'=>'true','data'=>$record]; + $response = json_encode($success_response); + print_r($response); + } + } catch (Exception $e) { + $isLoggedIn = false; + http_response_code(401); + print_r(json_encode(array( + "message" => "Access denied.", + "error" => $e->getMessage() + ))); + } +} +exit(); \ No newline at end of file diff --git a/api/projects.php b/api/projects.php new file mode 100755 index 00000000..4372816c --- /dev/null +++ b/api/projects.php @@ -0,0 +1,65 @@ +jwt; + +$isLoggedIn = false; +if ($jwt) { + try { + error_log($jwt); + $decoded = JWT::decode($jwt, $secret_key, array('HS256')); + $isLoggedIn = true; + $userId = $decoded->data->id; + $user = new ttUser(null, $userId); + if ($isLoggedIn) { + $project_list = ttTimeSweHelper::getprojects(); + foreach($project_list as &$value){ + $value[project]= ttTimeSweHelper::getprojectsname($value[project_id]); + } + $projects_with_null_clients = ttTimeSweHelper::getnullClientProjects(); + $success_response = ['active_clients'=>$project_list,'no clients'=>$projects_with_null_clients]; + $response = json_encode($success_response); + print_r($response); + } + } catch (Exception $e) { + $isLoggedIn = false; + http_response_code(401); + print_r(json_encode(array( + "message" => "Access denied.", + "error" => $e->getMessage() + ))); + } +} + +exit(); diff --git a/api/submitTime.php b/api/submitTime.php new file mode 100755 index 00000000..50a9aaeb --- /dev/null +++ b/api/submitTime.php @@ -0,0 +1,89 @@ +jwt; +$isLoggedIn = false; +if ($jwt) { + try { + $decoded = JWT::decode($jwt, $secret_key, array('HS256')); + $isLoggedIn = true; + $userId = $decoded->data->id; + $user = new ttUser(null, $userId); + if ($isLoggedIn) { + $group_id = $user->getGroup(); + $cl_date = $object->date; + $cl_project = $object->projectId; + $cl_duration = $object->duration; + $cl_note = $object->note; + $start = $object->start; + $finish = $object->finish; + $cl_client=$object->client_Id; + $cl_task = $object->task_Id; + // Insert record. + $id = ttTimeSweHelper::insert(array( + 'date' => $cl_date, + 'user_id' => $userId, + 'group_id' => $group_id, + 'client'=>$cl_client, + 'task'=> $cl_task, + 'org_id' => $user->org_id, + 'project' => $cl_project, + 'duration' => $cl_duration, + 'note' => $cl_note, + 'start' => $start, + 'finish'=>$finish, + 'billable' => true + )); + $result = true; + if ($id && $result && $err->no()) { + $success_response = ['success' => true, 'data' => $id]; + $response = json_encode($success_response); + print_r($response); + exit(); + } + } + } catch (Exception $e) { + $isLoggedIn = false; + http_response_code(401); + print_r(json_encode(array( + "message" => "Access denied.", + "error" => $e->getMessage() + ))); + } +} diff --git a/api/userlogin.php b/api/userlogin.php new file mode 100755 index 00000000..4b7c3cbb --- /dev/null +++ b/api/userlogin.php @@ -0,0 +1,80 @@ +login; +$cl_password = $object->password; + +@include('../plugins/limit/access_check.php'); +if ($auth->doLogin($cl_login, $cl_password)) { + $user = new ttUser(null, $auth->getUserId()); + + $isLoggedIn = false; + if (!empty($user)) { + $isLoggedIn = true; + } + if ($isLoggedIn) { + $secret_key = SECRET_KEY; + $issuer_claim = ISSUER_CLAIM; + $audience_claim = AUDIENCE_CLAIM; + $issuedat_claim = time(); // issued at + $notbefore_claim = $issuedat_claim; //not before in seconds + $expire_claim = $issuedat_claim + 1800; // expire time in seconds + $token = array( + "iss" => $issuer_claim, + "aud" => $audience_claim, + "iat" => $issuedat_claim, + "nbf" => $notbefore_claim, + "exp" => $expire_claim, + "data" => array( + "firstname" => $user->name, + "id" => $user->id, + "email" => $user->email + ) + ); + + http_response_code(200); + $jwt = JWT::encode($token, $secret_key); + + $success_response = ['success' => true, 'userId' => $user->id, 'jwt' => $jwt]; + $response = json_encode($success_response); + print_r($response); + } else { + $error_response = ['success' => false, 'error' => 'Empty user']; + $response = json_encode($error_response); + print_r($response); + } +} else { + $error_response = ['success' => false, 'error' => 'Failed the login']; + $response = json_encode($error_response); + print_r($response); +} +exit(); diff --git a/api/usersignup.php b/api/usersignup.php new file mode 100644 index 00000000..50dfc3c6 --- /dev/null +++ b/api/usersignup.php @@ -0,0 +1,77 @@ +manager_name; +$cl_manager_login = $object->manager_login; +$cl_password1 = $object->password1; +$cl_password2 = $object->password2; +$cl_manager_email = $object->manager_email; +$cl_group_name = $object->group_name; +$cl_currency = $object->currency; +$cl_lang = $object->language; +try { + $fields = array( + + 'user_name' => $cl_manager_name, + 'login' => $cl_manager_login, + 'password1' => $cl_password1, + 'password2' => $cl_password2, + 'email' => $cl_manager_email, + 'group_name' => $cl_group_name, + 'currency' => $cl_currency, + 'lang' => $cl_lang); + + //insert data into the database + import('ttRegistrator'); + $registrator = new ttRegistrator($fields, $err); + $registrator->register(); + +//check for error + if ($err->no()) { + $success_response = ['success' => true, 'user' => $cl_manager_name]; + $response = json_encode($success_response); + print_r($response); + exit(); + } + else + { + $success_response = ['success' => false, 'data' => $err]; + $response = json_encode($success_response); + print_r($response); + exit(); + } +} catch (Exception $e) { $isLoggedIn = false; + http_response_code(401); + print_r(json_encode(array( + "message" => "Access denied.", + "error" => $e->getMessage() + ))); + +} diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..c4818db1 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "firebase/php-jwt": "^5.2" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..c0a6eb57 --- /dev/null +++ b/composer.lock @@ -0,0 +1,68 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2e81f436193da5b40f951c6849eda663", + "packages": [ + { + "name": "firebase/php-jwt", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "feb0e820b8436873675fd3aca04f3728eb2185cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/feb0e820b8436873675fd3aca04f3728eb2185cb", + "reference": "feb0e820b8436873675fd3aca04f3728eb2185cb", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <=9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "time": "2020-03-25T18:49:23+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/docker-compose.yml b/docker-compose.yml index e621c410..3b9bb94a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,10 +28,17 @@ services: environment: MYSQL_RANDOM_ROOT_PASSWORD: "yes" MYSQL_DATABASE: timetracker - MYSQL_USER: anuko_user - MYSQL_PASSWORD: anuko_pw + MYSQL_USER: anuko_user_new + MYSQL_PASSWORD: anuko_pw_udian volumes: - anuko_db:/var/lib/mysql + + composer: + restart: 'no' + image: composer:latest + command: install + volumes: + - .:/app volumes: anuko_db: \ No newline at end of file diff --git a/dockerfile-tt b/dockerfile-tt index 9c79f478..90175d56 100644 --- a/dockerfile-tt +++ b/dockerfile-tt @@ -16,8 +16,8 @@ RUN apt-get update && apt-get install libpng-dev libfreetype6-dev -y \ && docker-php-ext-install gd # Install ldap extension. -RUN apt-get install libldap2-dev -y \ - && docker-php-ext-install ldap +#RUN apt-get install libldap2-dev -y \ +# && docker-php-ext-install ldap # TODO: check if ldap works, as the above is missing this step: # && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \ @@ -32,7 +32,7 @@ COPY . /var/www/html/ # Create configuration file. RUN cp /var/www/html/WEB-INF/config.php.dist /var/www/html/WEB-INF/config.php # Replace DSN value to something connectable to a Docker container running mariadb. -RUN sed -i "s|mysqli://root:no@localhost/dbname|mysqli://anuko_user:anuko_pw@anuko_db/timetracker|g" /var/www/html/WEB-INF/config.php +RUN sed -i "s|mysqli://root:no@localhost/dbname|mysqli://anuko_user_new:anuko_pw_udian@anuko_db/timetracker|g" /var/www/html/WEB-INF/config.php # Note that db is defined as anuko_db/timetracker where anuko_db is service name and timetracker is db name. # See docker-compose.yml for details.