Skip to content
This repository has been archived by the owner on Apr 22, 2021. It is now read-only.

With ldap auth #10

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
66db531
adding first little ldap auth prototype
JannikZed Jul 5, 2020
1fb3738
login / logout and api routes protected
JannikZed Jul 6, 2020
7bd9c6c
scoping settings
JannikZed Jul 6, 2020
1005dd2
first finished and tested version with ldap support
JannikZed Jul 6, 2020
84bb009
small naming changes
JannikZed Jul 15, 2020
3cc22d9
Merge branch 'master' into withAuth
JannikZed Jul 20, 2020
9c85313
adding group auth, changing settings scoping to user id, removing con…
JannikZed Jul 22, 2020
ff179b4
Merge branch 'withAuth' of https://github.com/owncloud/kimai-dashboar…
JannikZed Jul 22, 2020
4aead11
adding correct group search and user group model for booking page
JannikZed Jul 22, 2020
4596fef
several further upgrades and added cron now
JannikZed Jul 22, 2020
31d3302
fixing bugs for cron, adding mandatory server settings, enhancing doc…
JannikZed Jul 22, 2020
ff47e00
upgrading versions
JannikZed Sep 15, 2020
380bc03
upgrading to named exports
JannikZed Sep 15, 2020
6eb4750
Merge pull request #12 from owncloud/dependencies
JannikZed Sep 15, 2020
a907135
upgrading to new ldap project, several updates and upgrades
JannikZed Sep 16, 2020
ae65614
improving error handling, making groups optional
JannikZed Sep 17, 2020
c79fa8b
upgrading dependency - group lookup now with admin
JannikZed Oct 2, 2020
bf84fec
default value and removing error so nothing is shown in uri
JannikZed Oct 6, 2020
6155e60
switching to now released official package
JannikZed Oct 6, 2020
8a90797
adding import groups
JannikZed Oct 6, 2020
203dcec
new next-auth features
JannikZed Oct 6, 2020
4183695
import page group sec
JannikZed Oct 6, 2020
a6193d3
update
JannikZed Oct 6, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ LABEL maintainer="ownCloud GmbH <devops@owncloud.com>" \
org.label-schema.vendor="ownCloud GmbH" \
org.label-schema.schema-version="1.0"

ARG NEXT_PUBLIC_SITE
ENV NEXT_TELEMETRY_DISABLED=1
ENV NO_UPDATE_NOTIFIER=true
ENV NODE_ENV=production
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,29 @@ SMTP_SECURE=TRUE
SMTP_FROM_MAIL=
SMTP_USER=
SMTP_PASS=
# the URL where the dashboard is deployed (e.g. https://dashboard.example.com)
NEXT_PUBLIC_SITE="http://localhost:3000"
# The URL of the LDAP(s) server
LDAP_URL="ldap://localhost:389/"
BASE_DN="ou=people,dc=planetexpress,dc=com"
# Admin username used for searching groups and users
LDAP_ADMIN_USERNAME="cn=admin,dc=planetexpress,dc=com"
LDAP_ADMIN_PASSWORD="GoodNewsEveryone"
LDAP_MAPPING_UID="uid"
LDAP_MAPPING_NAME="displayName"
LDAP_MAPPING_MAIL="mail"
# Used to sign the JWT token
JWT_SECRET="ewrgw3456746hwrth35678356jh"
# the LDAP groups/ou that are allowed to access the dashboard
AUTH_GROUPS_DASHBOARD="Delivering Crew,Staff"
# the LDAP groups/ou that are allowed to access the time booking page
AUTH_GROUPS_BOOKING="Office Management,Staff"
```

## Build the docker container

When using helm to deploy the dashboard to Kubernetes, you need to match the container version with the appVersion of the Helm chart.

```Shell
cat deployment/Chart.yaml | grep appVersion
# increase app Version in `Chart.yaml` and `package.json` based on semver versioning
docker build -t kimai-dashboard:0.2.7 .
docker build -t kimai-dashboard:0.2.7 --build-arg NEXT_PUBLIC_SITE=http://localhost:3000 .
```

## Deploy to kubernetes with helm
Expand All @@ -48,6 +61,7 @@ docker run --rm \
-e SMTP_USER="smtp_user" \
-e SMTP_PASS="smtp_pass" \
-e SMTP_FROM_MAIL="kimai@example.com" \
-e SITE="http://localhost:3000" \
-p 3000:3000 \
kimai-dashboard
```
Expand Down
206 changes: 110 additions & 96 deletions backend_modules/cron.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const getMetaValue = (metaArray,key) => {
return singleMeta ? singleMeta.value : undefined;
};

const getMonthlyBooked = async () => {
const getMonthlyBooked = async (userId) => {
let toDate = new Date();
let fromDate = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000);
toDate = toDate.toJSON().substr(0,10);
Expand All @@ -33,15 +33,16 @@ const getMonthlyBooked = async () => {
return a;
},{});

let cron_users = await kimai.getSettings('users','cron');

let cron_users = await kimai.getSettings(userId, 'users','cron');
let cron_users_ids = cron_users.filter(u=>u.included).map(u=>u.id);
let booked_times = Object.values(total_spend_per_user)
.filter(u => cron_users_ids.includes(u.id))
.map(u => ({...cron_users.filter(db_u=>db_u.id===u.id)[0],...u}));
return booked_times;
};

const getUnderbookedUsers = async () => {
const getUnderbookedUsers = async (userId) => {
let toDate = new Date();
let fromDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
toDate = toDate.toJSON().substr(0,10);
Expand All @@ -62,7 +63,7 @@ const getUnderbookedUsers = async () => {
return a;
},{});

let cron_users = await kimai.getSettings('users','cron');
let cron_users = await kimai.getSettings(userId, 'users','cron');
let cron_users_ids = cron_users.filter(u=>u.included).map(u=>u.id);
let booked_times = Object.values(total_spend_per_user)
.filter(u => u.userTimeTotalSpend < 20 * 60 * 60) // 20h
Expand Down Expand Up @@ -127,7 +128,7 @@ const SMTP_FROM_MAIL = process.env.SMTP_FROM_MAIL;
assert(SMTP_USER, "env var SMTP_USER not set");
assert(SMTP_PASS, "env var SMTP_PASS not set");
assert(SMTP_FROM_MAIL, "env var SMTP_FROM_MAIL not set");
const sendMail = async (subject, text, html) => {
const sendMail = async (userId, subject, text, html) => {
const mail = nodemailer.createTransport({
host: process.env.SMTP_HOST || "smtp.mailgun.org",
port: Number(process.env.SMTP_PORT) || 456,
Expand All @@ -139,7 +140,7 @@ const sendMail = async (subject, text, html) => {
});

// send mail with defined transport object
let reportEmails = await kimai.getSettings('reportEmails','reportEmails');
let reportEmails = await kimai.getSettings(userId, 'reportEmails','reportEmails');
if(!reportEmails || (reportEmails && reportEmails.length===0)) {
console.warn("WARNING: no emails specified");
return;
Expand All @@ -157,103 +158,116 @@ const sendMail = async (subject, text, html) => {

const registerJobs = async () => {

const users = await kimai.getAllUsers() || []

for (const user of users){
console.log("register critical projects job at: 0 0 0 * * * for user", user);
new CronJob('0 0 0 * * *', async () => { //every 1 day
// Everyday a cronjob is checking if
// projektbudget – booked time < 80% -> Mail to dlindner@owncloud.com and john@owncloud.com
const critical_budgets = await getCriticalProjects();
console.log("sending critical budget report with ",critical_budgets.length,"critical projects");
let email_txt = `
Here is your daily report of projects with critical time budgets (total time booked / project budget >= 80%):

Percent booked: Total time booked: Project budget: Name:\n`;
let email_html = `
<p>Here is your daily report of projects with critical time budgets (total time booked / project budget >= 80%):</p>

<table>
<tr>
<th>Name</th>
<th>Total time booked</th>
<th>Project budget</th>
<th>Percent booked</th>
</tr>`;
critical_budgets.forEach((proj)=>{
const percent = (proj.projectTimeTotalSpend/proj.projectTimeBudget*100).toFixed(2).padStart("Percent booked".length, " ");
const total = formatSeconds(proj.projectTimeTotalSpend).padStart("Total time booked".length, " ");
const budget = formatSeconds(proj.projectTimeBudget).padStart("Project budget".length, " ");
email_txt += `${percent}% ${total}h ${budget}h ${proj.projectName}\n`;
email_html += `<tr><td>${proj.projectName}</td><td>${total}h</td><td>${budget}h</td><td>${percent}%</td></tr>`;
});
email_html += `</table>`;
const res = await sendMail(user, "Projects with critical time budget - "+new Date().toJSON().substr(0,10),email_txt,email_html);
console.log("report email accepted by",res.accepted,"status:",res.response);
}).start();

}

console.log("register critical projects job at: 0 0 0 * * *");
new CronJob('0 0 0 * * *', async () => { //every 1 day
// Everyday a cronjob is checking if
// projektbudget – booked time < 80% -> Mail to dlindner@owncloud.com and john@owncloud.com
const critical_budgets = await getCriticalProjects();
console.log("sending critical budget report with ",critical_budgets.length,"critical projects");
let email_txt = `
Here is your daily report of projects with critical time budgets (total time booked / project budget >= 80%):

Percent booked: Total time booked: Project budget: Name:\n`;
let email_html = `
<p>Here is your daily report of projects with critical time budgets (total time booked / project budget >= 80%):</p>
for (const user of users){
console.log("register underbooked users job at: 0 0 12 * * 5 for user", user);
new CronJob('0 0 12 * * 5', async () => { //every Friday, (friday is 5)
// new CronJob('0 24 11 * * *', async () => { //every Friday, (friday is 5)
// There is a cronjob every week Friday 12:00 and check if every person from our team booked minimum 20h – if not it sends me a Mail with name and hours - dlindner@owncloud.com example: Martin 16h (please do a config.txt so i can add people)
const underbooked_users = await getUnderbookedUsers(user);
if(underbooked_users.length === 0) {
console.warn("no underbooked users");
return;
}
console.log("sending underbooked users report with ",underbooked_users.length,"underbooked users");
let email_txt = `
Here is your weekly report of underbooked users (team members with less than 20h booked):

<table>
<tr>
<th>Name</th>
<th>Total time booked</th>
<th>Project budget</th>
<th>Percent booked</th>
</tr>`;
critical_budgets.forEach((proj)=>{
const percent = (proj.projectTimeTotalSpend/proj.projectTimeBudget*100).toFixed(2).padStart("Percent booked".length, " ");
const total = formatSeconds(proj.projectTimeTotalSpend).padStart("Total time booked".length, " ");
const budget = formatSeconds(proj.projectTimeBudget).padStart("Project budget".length, " ");
email_txt += `${percent}% ${total}h ${budget}h ${proj.projectName}\n`;
email_html += `<tr><td>${proj.projectName}</td><td>${total}h</td><td>${budget}h</td><td>${percent}%</td></tr>`;
});
email_html += `</table>`;
const res = await sendMail("Projects with critical time budget - "+new Date().toJSON().substr(0,10),email_txt,email_html);
console.log("report email accepted by",res.accepted,"status:",res.response);
}).start();
Alias: Username: Booked time:\n`;
let email_html = `
<p>Here is your weekly report of underbooked users (team members with less than 20h booked):</p>

<table>
<tr>
<th>Alias</th>
<th>Username</th>
<th>Booked time</th>
</tr>`;
underbooked_users.forEach((u)=>{
const total = formatSeconds(u.userTimeTotalSpend);
email_txt += `${u.alias.padEnd("Alias: ".length, " ")} ${u.username.padEnd("Username: ".length, " ")} ${total}h\n`;
email_html += `<tr><td>${u.alias}</td><td>${u.username}</td><td>${total}h</td></tr>`;
});
email_html += `</table>`;
const res = await sendMail("Underbooked users report for - "+new Date().toJSON().substr(0,10),email_txt,email_html);
console.log("report email accepted by",res.accepted,"status:",res.response);
}).start();

console.log("register underbooked users job at: 0 0 12 * * 5");
new CronJob('0 0 12 * * 5', async () => { //every Friday, (friday is 5)
// new CronJob('0 24 11 * * *', async () => { //every Friday, (friday is 5)
// There is a cronjob every week Friday 12:00 and check if every person from our team booked minimum 20h – if not it sends me a Mail with name and hours - dlindner@owncloud.com example: Martin 16h (please do a config.txt so i can add people)
const underbooked_users = await getUnderbookedUsers();
if(underbooked_users.length === 0) {
console.warn("no underbooked users");
return;
}
console.log("sending underbooked users report with ",underbooked_users.length,"underbooked users");
let email_txt = `
Here is your weekly report of underbooked users (team members with less than 20h booked):
}

Alias: Username: Booked time:\n`;
let email_html = `
<p>Here is your weekly report of underbooked users (team members with less than 20h booked):</p>
for (const user of users){
console.log("monthly user report job at: 0 0 0 1 * * for user", user);
new CronJob('0 0 0 1 * *', async () => { //every month on the first
// new CronJob('0 36 11 * * *', async () => { //every month on the first
// There is a cronjob every month tells me via Mail which person booked how much hours - dlindner@owncloud.com - Example Kevin 160h, David 180h (please do a config.txt so i can add people) -> Team auswählen für Cronjob, für beide cronjobs das gleiche
const monthly_booked = await getMonthlyBooked(user);
if(monthly_booked.length === 0) {
console.warn("no underbooked users");
return;
}
console.log("sending team member report with ",monthly_booked.length,"team members");
let email_txt = `
Here is your weekly report of underbooked users (team members with less than 20h booked):

<table>
<tr>
<th>Alias</th>
<th>Username</th>
<th>Booked time</th>
</tr>`;
underbooked_users.forEach((u)=>{
const total = formatSeconds(u.userTimeTotalSpend);
email_txt += `${u.alias.padEnd("Alias: ".length, " ")} ${u.username.padEnd("Username: ".length, " ")} ${total}h\n`;
email_html += `<tr><td>${u.alias}</td><td>${u.username}</td><td>${total}h</td></tr>`;
});
email_html += `</table>`;
const res = await sendMail("Underbooked users report for - "+new Date().toJSON().substr(0,10),email_txt,email_html);
console.log("report email accepted by",res.accepted,"status:",res.response);
}).start();
console.log("monthly user report job at: 0 0 0 1 * *");
new CronJob('0 0 0 1 * *', async () => { //every month on the first
// new CronJob('0 36 11 * * *', async () => { //every month on the first
// There is a cronjob every month tells me via Mail which person booked how much hours - dlindner@owncloud.com - Example Kevin 160h, David 180h (please do a config.txt so i can add people) -> Team auswählen für Cronjob, für beide cronjobs das gleiche
const monthly_booked = await getMonthlyBooked();
if(monthly_booked.length === 0) {
console.warn("no underbooked users");
return;
}
console.log("sending team member report with ",monthly_booked.length,"team members");
let email_txt = `
Here is your weekly report of underbooked users (team members with less than 20h booked):
Alias: Username: Booked time:\n`;
let email_html = `
<p>Here is your monthly report of booked hours per team member:</p>

<table>
<tr>
<th>Alias</th>
<th>Username</th>
<th>Booked time</th>
</tr>`;
monthly_booked.forEach((u)=>{
const total = formatSeconds(u.userTimeTotalSpend);
email_txt += `${u.alias.padEnd("Alias: ".length, " ")} ${u.username.padEnd("Username: ".length, " ")} ${total}h\n`;
email_html += `<tr><td>${u.alias}</td><td>${u.username}</td><td>${total}h</td></tr>`;
});
email_html += `</table>`;
const res = await sendMail("Monthly booked per team member report - "+new Date().toJSON().substr(0,10),email_txt,email_html);
console.log("report email accepted by",res.accepted,"status:",res.response);
}).start();

}

Alias: Username: Booked time:\n`;
let email_html = `
<p>Here is your monthly report of booked hours per team member:</p>

<table>
<tr>
<th>Alias</th>
<th>Username</th>
<th>Booked time</th>
</tr>`;
monthly_booked.forEach((u)=>{
const total = formatSeconds(u.userTimeTotalSpend);
email_txt += `${u.alias.padEnd("Alias: ".length, " ")} ${u.username.padEnd("Username: ".length, " ")} ${total}h\n`;
email_html += `<tr><td>${u.alias}</td><td>${u.username}</td><td>${total}h</td></tr>`;
});
email_html += `</table>`;
const res = await sendMail("Monthly booked per team member report - "+new Date().toJSON().substr(0,10),email_txt,email_html);
console.log("report email accepted by",res.accepted,"status:",res.response);
}).start();
};
exports.registerJobs = registerJobs;

Expand Down
45 changes: 40 additions & 5 deletions backend_modules/kimai.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ db.defaults({ projects:{} })
.write();

const fetchKimai = async (path) => {
console.log("fetchKimai init:",KIMAI_API_URL + path);
const result = await fetch(KIMAI_API_URL + path, {
headers: {
'X-AUTH-USER': KIMAI_API_USER,
Expand Down Expand Up @@ -57,18 +56,53 @@ const postKimai = async (path,body) => {
return json;
};

const getSettings = async (key,id) => {
/**
* get an array of all users that have settings in the DB. Used for Cron function.
*/
const getAllUsers = async () => {
try {
return await db.get('allUsers').value();
} catch (e) {
console.dir(e);
}
}

/**
* Get values from the JSON DB
* @param {string} userId the user ID from LDAP
* @param {string} key
* @param {*} id
*/
const getSettings = async (userId,key,id) => {
try{
//await Parallel.each(project_details, async (proj) => db.set('projects.'+proj.id, proj).write(), 1);
return await db.get(key+'_'+id).value();
return await db.get(userId+'_'+key+'_'+id).value();
} catch(e){
console.dir(e);
}
};
const setSettings = async (key,id,data) => {
/**
* Set values in the JSON DB. Automatically saves the dbb value 'allUsers' as an array of all users available.
* @param {*} userId the user ID coming from LDAP
* @param {*} key
* @param {*} id
* @param {*} data
*/
const setSettings = async (userId,key,id,data) => {
try{
const allUsers = await db.get('allUsers').value();
if(!allUsers) {
const users = []
users.push(userId)
await db.set('allUsers', users).write()
} else {
if(!allUsers.includes(userId)){
allUsers.push(userId)
await db.set('allUsers', allUsers).write()
}
}
//await Parallel.each(project_details, async (proj) => db.set('projects.'+proj.id, proj).write(), 1);
return await db.set(key+'_'+id,data).write();
return await db.set(userId+'_'+key+'_'+id,data).write();
} catch(e){
console.dir(e);
}
Expand Down Expand Up @@ -99,6 +133,7 @@ exports.fetchKimai = fetchKimai;
exports.postKimai = postKimai;
exports.getSettings = getSettings;
exports.setSettings = setSettings;
exports.getAllUsers = getAllUsers;

exports.DEPRECATED_getAllProjectDetails = DEPRECATED_getAllProjectDetails;
exports.DEPRECATED_getProjectDetailsById = DEPRECATED_getProjectDetailsById;
Expand Down
2 changes: 0 additions & 2 deletions modules/charts/1_projectTotalSpend.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import querystring from 'querystring';

async function fetcher(path, fromDate, toDate) {
const query = querystring.stringify({fromDate:fromDate.toJSON().substr(0,10), toDate:toDate.toJSON().substr(0,10)});
console.log("querry",query);
const res = await fetch(path+'?'+query);
console.log(res);
const json = await res.json();
return json;
}
Expand Down
Loading