Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Authors Access #324

Merged
merged 88 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 83 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
cb94bd8
TO DO: add dropdown list to start tab
sorochak Feb 27, 2024
45e0166
query db for list of user emails
sorochak Feb 28, 2024
c9529b5
add autocomplete input to select email to share editing with
sorochak Feb 28, 2024
da029a7
upate security rules
sorochak Feb 29, 2024
f491467
adds an array of emails to the sharedWith field
sorochak Feb 29, 2024
9e22fa5
get the user object and not just the email
sorochak Feb 29, 2024
bbceabd
process user emails after users are fetched
sorochak Feb 29, 2024
420be88
update rules to allow write to sharedWithMe
sorochak Feb 29, 2024
44e6513
fix rules
sorochak Feb 29, 2024
4516e78
update rules for new db structure
sorochak Mar 6, 2024
2650dbf
add storeSharedRecord function
sorochak Mar 6, 2024
1f2ab28
add UI to share a record with other users
sorochak Mar 6, 2024
2b55970
user email only for label, store userID
sorochak Mar 7, 2024
847d327
Merge remote-tracking branch 'origin/main' into 78-multiple-authors-a…
sorochak Mar 7, 2024
3d24383
move 'shared with' feature to its own component
sorochak Mar 7, 2024
a1ce852
strip some comments
sorochak Mar 7, 2024
90084cf
Merge remote-tracking branch 'origin/main' into 78-multiple-authors-a…
sorochak Mar 8, 2024
d98c977
Merge remote-tracking branch 'origin/main' into 78-multiple-authors-a…
sorochak Mar 8, 2024
e8c3c54
check if user hasSharedRecords and render shared with me in Nav
sorochak Mar 8, 2024
da96f48
style shared users list
sorochak Mar 9, 2024
fac7742
update style
sorochak Mar 9, 2024
a4683c3
add Shared page
sorochak Mar 9, 2024
8b0cfa9
make button label bilingual
sorochak Mar 9, 2024
a8c4077
use listener to get hasSharedRecords
sorochak Mar 11, 2024
928611d
finish shared page
sorochak Mar 11, 2024
afdc0bf
Merge remote-tracking branch 'origin/main' into 78-multiple-authors-a…
sorochak Mar 15, 2024
bb74cd2
fix typo
sorochak Mar 18, 2024
a0a9edb
update shares structure
sorochak Mar 19, 2024
1e4ceaf
pass authorID to updateSharedRecord
sorochak Mar 19, 2024
64fe981
fix loadSharedRecords to structure record properly
sorochak Mar 20, 2024
0c05026
fix path in editRecord() to contain authorID instead of currentUser ID
sorochak Mar 20, 2024
c9d559a
fix loggedInUserCanEditRecord so that shared records can be edited
sorochak Mar 20, 2024
25f799b
make rules file consistent with console
sorochak Mar 20, 2024
0fab3b4
fix permissions denied error on write to shared record
sorochak Mar 20, 2024
991202f
move recordToDataCite() to firebase functions to access admin credent…
sorochak Mar 22, 2024
6b07d74
remove recordToDataCite test
sorochak Mar 22, 2024
db0ae4d
createDraftDoi queries authHash from db instead of receiving as param…
sorochak Mar 22, 2024
3b1dd03
updateDraftDoi queries authHash from db
sorochak Mar 22, 2024
118a452
deleteDraftDoi queries creds from db
sorochak Mar 22, 2024
936f26d
getDoiStatus queries for creds
sorochak Mar 22, 2024
696b507
remove datacite cred state variables
sorochak Mar 22, 2024
3cb09f5
remove unused getAuthHash function and unit test
sorochak Mar 22, 2024
36df4b5
version firebase dev functions with PR number
sorochak Mar 22, 2024
a3204e3
fix
sorochak Mar 22, 2024
5925a2c
remove _ from firebase function names
sorochak Mar 22, 2024
1aa5bd8
use versioned firebase functions for development
sorochak Mar 22, 2024
c3b78ca
fix versioned function calls
sorochak Mar 22, 2024
08ec3e1
fix get doi status dev
sorochak Mar 22, 2024
32bf7c3
add getCredentialsStored to firebase functions
sorochak Mar 25, 2024
7b3834e
remove getCredentialsStored from client side
sorochak Mar 25, 2024
b84a62f
Admin page uses getCredentialsStored from UserContext
sorochak Mar 25, 2024
4ca3e2b
conditionally render generate doi button based on credentials stored
sorochak Mar 25, 2024
03dcd3b
add doi deleted flag, and add margin right to buttons
sorochak Mar 25, 2024
ccdb65e
remove getCredentialsStored test
sorochak Mar 25, 2024
51b806e
limit record sharing to author
sorochak Mar 25, 2024
8e906bb
add check for new record
sorochak Mar 25, 2024
e4cc382
disable share record button for new records
sorochak Mar 25, 2024
f3c4af0
update useEffect dependency list
sorochak Mar 25, 2024
85f3f39
fix unshare record
sorochak Mar 26, 2024
2932683
render submit button only if original author
sorochak Mar 26, 2024
b856548
Merge remote-tracking branch 'origin/main' into 78-multiple-authors-a…
sorochak Apr 10, 2024
54358c8
update required changes to recordToDataCite
sorochak Apr 10, 2024
2457b11
cleanup UserProvider
sorochak Apr 10, 2024
e238e16
update package.json
sorochak Apr 11, 2024
c7eeeff
fix up merge
fostermh Apr 12, 2024
ec8c299
remove unused function names and variables
fostermh Apr 12, 2024
31b5600
convert console errors to throw and fix share database set and remove
fostermh Apr 12, 2024
e7f4a04
Merge pull request #348 from cioos-siooc/resolve_merge_to_multi_autho…
sorochak Apr 12, 2024
051b3fc
update firebase config
sorochak Apr 12, 2024
b2afaf3
fix structure of rules file
sorochak Apr 12, 2024
b4986b0
fix prefix return
sorochak Apr 15, 2024
d43772c
add margins to buttons
sorochak Apr 15, 2024
54bf7c9
separate doi cred save functionality
sorochak Apr 15, 2024
edff197
fix Buffer not Defined Error
sorochak Apr 15, 2024
481145c
move save button
sorochak Apr 15, 2024
2a856d7
remove recordToDataCite Firebase Function
sorochak Apr 15, 2024
1c7f7ce
remove tests for functions moved to the cloud
sorochak Apr 15, 2024
f983358
use names instead of emails for sharing records
sorochak Apr 15, 2024
a4d0cf9
fix Shared page errors - update database interactions
sorochak Apr 16, 2024
1024ccc
change email to name in helper text
sorochak Apr 16, 2024
77b4599
use dev project for deploying to preview url
sorochak Apr 17, 2024
2b7a3e0
Auto stash before merge of "78-multiple-authors-access" and "origin/7…
fostermh Apr 17, 2024
c07f97e
split prod and dev config
fostermh Apr 17, 2024
61da119
update dev db url
sorochak Apr 17, 2024
3987e98
Merge remote-tracking branch 'origin/main' into 78-multiple-authors-a…
sorochak Apr 17, 2024
71cfa44
fix cloneRecord in Shared
sorochak Apr 17, 2024
4af75df
fix handleCloneRecord call
sorochak Apr 17, 2024
1261175
update README to outline deploying dev functions to dev project for p…
sorochak Apr 17, 2024
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
9 changes: 6 additions & 3 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"projects": {
"default": "cioos-metadata-form"
}
}
"default": "cioos-metadata-form",
"dev": "cioos-metadata-form-dev"
},
"targets": {},
"etags": {}
}
2 changes: 1 addition & 1 deletion .github/workflows/firebase-hosting-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_CIOOS_METADATA_FORM }}'
expires: 30d
entryPoint: '.'
projectId: cioos-metadata-form
projectId: cioos-metadata-form-dev
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
GMAIL_USER: ${{ secrets.GMAIL_USER }}
Expand Down
13 changes: 12 additions & 1 deletion firebase-functions/.firebaserc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"projects": {
"default": "cioos-metadata-form"
"default": "cioos-metadata-form",
"dev": "cioos-metadata-form-dev"
},
"targets": {
"cioos-metadata-form": {
Expand All @@ -12,6 +13,16 @@
"cioos-metadata-form-dev"
]
}
},
"development": {
"database": {
"prod": [
"cioos-metadata-form"
],
"dev": [
"cioos-metadata-form-dev"
]
}
}
},
"etags": {}
Expand Down
64 changes: 34 additions & 30 deletions firebase-functions/database.rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// This design requires careful structuring of rules (and data) to ensure appropriate access control
// throughout the database hierarchy.
"$region": {
// Allow read access to any authenticated user.
// Allow read access to any authenticated user.
".read": "auth.uid != null",
"shares": {
"$userid": {
Expand All @@ -17,48 +17,52 @@
"users": {
"$userid": {
// Allow write access if the authenticated user is the user specified by $userid or if the authenticated user's email is listed as a reviewer in the admin permissions for the region.
".write": "(auth.uid == $userid) || root.child('admin').child($region).child('permissions').child('reviewers').val().contains(auth.email)",
".write": "(auth.uid == $userid) || root.child('admin').child($region).child('permissions').child('reviewers').val().contains(auth.email)",
"records": {
"$recordid": {
// Allow read access if the authenticated users's uid is listed in the 'shared with' list for the record
".read": "root.child($region).child('users').child($userid).child('records').child($recordid).child('sharedWith').child(auth.uid).exists()",
// Allow write access if the authenticated users's uid is listed in the 'shared with' list for the record
".write": "root.child($region).child('users').child($userid).child('records').child($recordid).child('sharedWith').child(auth.uid).exists()",
// Allow read access if the authenticated users's uid exists in the 'shared with' object for the record
".read": "root.child($region).child('users').child($userid).child('records').child($recordid).child('sharedWith').child(auth.uid).exists()",
// Allow write access if the authenticated users's uid exists in the 'shared with' object for the record
".write": "root.child($region).child('users').child($userid).child('records').child($recordid).child('sharedWith').child(auth.uid).exists()",
}
},
}
}
}
},
"admin": { // Section of the database dedicated to admin configurations.
"$regionAdmin": {
// Allow read access if the authenticated user's email is listed as a reviewer in the permissions for the region.
".read": "root.child('admin').child($regionAdmin).child('permissions').child('reviewers').val().contains(auth.email) || root.child('admin').child($regionAdmin).child('permissions').child('admins').val().contains(auth.email)",
"projects": {
// Allow write access to projects if the authenticated user's email is listed as a reviewer in the permissions for the region.
".write": "root.child('admin').child($regionAdmin).child('permissions').child('reviewers').val().contains(auth.email)",
// Allow read access to any authenticated user.
".read": "auth.uid != null"
},
"permissions": {
// Allow read access to any authenticated user.
".read": "auth.uid != null",
"admins": {
// Allow write access for admins if the authenticated user's email is listed as an admin in the permissions for the region.
".write": "root.child('admin').child($regionAdmin).child('permissions').child('admins').val().contains(auth.email)",
".read": "root.child('admin').child($regionAdmin).child('permissions').child('reviewers').val().contains(auth.email)",
"projects": {
// Allow write access to projects if the authenticated user's email is listed as a reviewer in the permissions for the region.
".write": "root.child('admin').child($regionAdmin).child('permissions').child('reviewers').val().contains(auth.email)",
// Allow read access to any authenticated user.
".read": "auth.uid != null"
},
"permissions": {
// Allow read access to any authenticated user.
".read": "auth.uid != null"
".read": "auth.uid != null",
"admins": {
// Allow write access for admins if the authenticated user's email is listed as an admin in the permissions for the region.
".write": "root.child('admin').child($regionAdmin).child('permissions').child('admins').val().contains(auth.email)",
// Allow read access to any authenticated user.
".read": "auth.uid != null",
},
"reviewers": {
// Allow write access for reviewers if the authenticated user's email is listed as an admin in the permissions for the region.
".write": "root.child('admin').child($regionAdmin).child('permissions').child('admins').val().contains(auth.email)",
// Allow read access to any authenticated user.
".read": "auth.uid != null",
},
"dataciteCredentials": {
// Allow write access to DataCite credentials if the authenticated user's email is listed as an admin in the permissions for the region.
".write": "root.child('admin').child($regionAdmin).child('permissions').child('admins').val().contains(auth.email)",
}
},
"reviewers": {
// Allow write access for reviewers if the authenticated user's email is listed as an admin in the permissions for the region.
"dataciteCredentials": {
// Allow write access to DataCite credentials if the authenticated user's email is listed as an admin in the permissions for the region.
".write": "root.child('admin').child($regionAdmin).child('permissions').child('admins').val().contains(auth.email)",
// Allow read access to any authenticated user.
".read": "auth.uid != null"
}
},
"dataciteCredentials": {
// Allow write access to DataCite credentials if the authenticated user's email is listed as an admin in the permissions for the region.
".write": "root.child('admin').child($regionAdmin).child('permissions').child('admins').val().contains(auth.email)",
}
}
}
}
Expand Down
94 changes: 81 additions & 13 deletions firebase-functions/functions/datacite.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
const admin = require("firebase-admin");

const baseUrl = "https://api.datacite.org/dois/";
const functions = require("firebase-functions");
const { defineString } = require('firebase-functions/params');
const axios = require("axios");

exports.createDraftDoi = functions.https.onCall(async (data) => {

const { record, authHash } = data;
const { record, region } = data;

let authHash

try {
authHash = (await admin.database().ref('admin').child(region).child("dataciteCredentials").child("dataciteHash").once("value")).val();
} catch (error) {
console.error(`Error fetching Datacite Auth Hash for region ${region}:`, error);
return null;
}

functions.logger.log(authHash);

Expand Down Expand Up @@ -51,15 +61,21 @@ exports.createDraftDoi = functions.https.onCall(async (data) => {
}
});

exports.updateDraftDoi = functions.https.onCall(async (data) => {

const dataciteCred = process.env.DATACITE_AUTH_HASH || dataciteAuthHash.value()
exports.updateDraftDoi = functions.https.onCall(async (dataObj) => {
const { doi, region, data } = dataObj;
let authHash
try {
authHash = (await admin.database().ref('admin').child(region).child("dataciteCredentials").child("dataciteHash").once("value")).val();
} catch (error) {
console.error(`Error fetching Datacite Auth Hash for region ${region}:`, error);
return null;
}

try {
const url = `${baseUrl}${data.doi}/`;
const response = await axios.put(url, data.data, {
const url = `${baseUrl}${doi}/`;
const response = await axios.put(url, data, {
headers: {
'Authorization': `Basic ${data.dataciteAuthHash}`,
'Authorization': `Basic ${authHash}`,
'Content-Type': "application/json",
},
});
Expand Down Expand Up @@ -102,11 +118,20 @@ exports.updateDraftDoi = functions.https.onCall(async (data) => {

exports.deleteDraftDoi = functions.https.onCall(async (data) => {

const { doi, dataciteAuthHash } = data;
const { doi, region } = data;
let authHash

try {
authHash = (await admin.database().ref('admin').child(region).child("dataciteCredentials").child("dataciteHash").once("value")).val();
} catch (error) {
console.error(`Error fetching Datacite Auth Hash for region ${region}:`, error);
return null;
}

try {
const url = `${baseUrl}${doi}/`;
const response = await axios.delete(url, {
headers: { 'Authorization': `Basic ${dataciteAuthHash}` },
headers: { 'Authorization': `Basic ${authHash}` },
});
return response.status;
} catch (err) {
Expand Down Expand Up @@ -142,14 +167,31 @@ exports.deleteDraftDoi = functions.https.onCall(async (data) => {

exports.getDoiStatus = functions.https.onCall(async (data) => {

const dataciteCred = process.env.DATACITE_AUTH_HASH || dataciteAuthHash.value()
let prefix;
let authHash

functions.logger.log(data);

try {
prefix = (await admin.database().ref('admin').child(data.region).child("dataciteCredentials").child("prefix").once("value")).val();
} catch (error) {
console.error(`Error fetching Datacite Prefix for region ${data.region}:`, error);
return null;
}

try {
authHash = (await admin.database().ref('admin').child(data.region).child("dataciteCredentials").child("dataciteHash").once("value")).val();
} catch (error) {
console.error(`Error fetching Datacite Auth Hash for region ${data.region}:`, error);
return null;
}

try {
const url = `${baseUrl}${data.doi}/`;
// TODO: limit response to just the state field. elasticsearch query syntax?
const response = await axios.get(url, {
headers: {
'Authorization': `Basic ${data.authHash}`
'Authorization': `Basic ${authHash}`
},
});
return response.data.data.attributes.state;
Expand All @@ -163,7 +205,7 @@ exports.getDoiStatus = functions.https.onCall(async (data) => {
}
// if the error is a 404, throw a HttpsError with the code 'not-found'
if (err.response && err.response.status === 404) {
if (data.doi.startsWith(`${data.prefix}/`)) {
if (data.doi.startsWith(`${prefix}/`)) {
return 'not found'
}
return 'unknown'
Expand All @@ -184,3 +226,29 @@ exports.getDoiStatus = functions.https.onCall(async (data) => {
}

});

exports.getCredentialsStored = functions.https.onCall(async (data) => {
try {
const credentialsRef = admin.database().ref('admin').child(data).child("dataciteCredentials");
const authHashSnapshot = await credentialsRef.child("dataciteHash").once("value");
const prefixSnapshot = await credentialsRef.child("prefix").once("value");

const authHash = authHashSnapshot.val();
const prefix = prefixSnapshot.val();

// Check for non-null and non-empty
return authHash && authHash !== "" && prefix && prefix !== "";
} catch (error) {
console.error("Error checking Datacite credentials:", error);
return false;
}
});

exports.getDatacitePrefix = functions.https.onCall(async (region) => {
try {
const prefix = (await admin.database().ref('admin').child(region).child("dataciteCredentials").child("prefix").once("value")).val();
return prefix;
} catch (error) {
throw new Error(`Error fetching Datacite Prefix for region ${region}: ${error}`);
}
});
4 changes: 3 additions & 1 deletion firebase-functions/functions/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const admin = require("firebase-admin");
const { translate } = require("./translate");
const { checkURLActive } = require("./serverUtils");
const { createDraftDoi, updateDraftDoi, deleteDraftDoi, getDoiStatus } = require("./datacite");
const { createDraftDoi, updateDraftDoi, deleteDraftDoi, getDoiStatus, getCredentialsStored, getDatacitePrefix } = require("./datacite");
const { notifyReviewer, notifyUser } = require("./notify");
const {
updatesRecordCreate,
Expand All @@ -26,3 +26,5 @@ exports.deleteDraftDoi = deleteDraftDoi;
exports.updateDraftDoi = updateDraftDoi;
exports.getDoiStatus = getDoiStatus;
exports.checkURLActive = checkURLActive;
exports.getCredentialsStored = getCredentialsStored;
exports.getDatacitePrefix = getDatacitePrefix;
36 changes: 31 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading