Skip to content

Commit

Permalink
sharing now always distributing keys on encrypted nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
Clay-Ferguson committed Dec 15, 2024
1 parent c291392 commit 6bd92ca
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 84 deletions.
12 changes: 9 additions & 3 deletions src/main/java/quanta/mongo/MongoAuth.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,9 @@ public void ownerAuth(SubNode node) {
}

if (!TL.getSC().getUserNodeObjId().equals(node.getOwner())) {
throw new ForbiddenException("Unable to save Node (expected ownerId " + TL.getSC().getUserNodeObjId().toHexString()
+ ") but found ownerId " + node.getIdStr() + " on the session) for node: "
+ XString.prettyPrint(node));
throw new ForbiddenException("Unable to save Node (expected ownerId "
+ TL.getSC().getUserNodeObjId().toHexString() + ") but found ownerId " + node.getIdStr()
+ " on the session) for node: " + XString.prettyPrint(node));
}
}

Expand Down Expand Up @@ -419,6 +419,11 @@ public boolean nodeAuth(SubNode node, String sessionUserNodeId, List<PrivilegeTy
return false;
}

/*
* todo-0: this appears to return "rd,wr" as a single string in the privileges.privilegeName,
* whereas the SubNode itself I think stores them as separate strings in the AccessControl.prvs
* field. This is a bit confusing.
*/
public List<AccessControlInfo> getAclEntries(SubNode node) {
HashMap<String, AccessControl> aclMap = node.getAc();
if (aclMap == null) {
Expand Down Expand Up @@ -448,6 +453,7 @@ public AccessControlInfo createAccessControlInfo(String principalId, String auth
}
// else we need the user name
else {
// todo-0: this can be AccountNode here?
SubNode principalNode = svc_mongoRead.getNodeAP(principalId);
if (principalNode == null) {
return null;
Expand Down
22 changes: 17 additions & 5 deletions src/main/java/quanta/rest/response/AddPrivilegeResponse.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

package quanta.rest.response;

import java.util.List;
import quanta.model.AccessControlInfo;
import quanta.rest.response.base.ResponseBase;

public class AddPrivilegeResponse extends ResponseBase {
Expand All @@ -10,23 +12,33 @@ public class AddPrivilegeResponse extends ResponseBase {
* browser encrypts a key to send back to the server
*/
private String principalNodeId;


// returns back to server the acl entries, in case encryption keys need to be processed (optional)
private List<AccessControlInfo> aclEntries;

public String getPrincipalPublicKey() {
return this.principalPublicKey;
}

public String getPrincipalNodeId() {
return this.principalNodeId;
}

public void setPrincipalPublicKey(final String principalPublicKey) {
this.principalPublicKey = principalPublicKey;
}

public void setPrincipalNodeId(final String principalNodeId) {
this.principalNodeId = principalNodeId;
}

public AddPrivilegeResponse() {
public List<AccessControlInfo> getAclEntries() {
return this.aclEntries;
}

public void setAclEntries(final List<AccessControlInfo> aclEntries) {
this.aclEntries = aclEntries;
}

public AddPrivilegeResponse() {}
}
11 changes: 5 additions & 6 deletions src/main/java/quanta/rest/response/SaveNodeResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,18 @@ public class SaveNodeResponse extends ResponseBase {
public NodeInfo getNode() {
return this.node;
}

public List<AccessControlInfo> getAclEntries() {
return this.aclEntries;
}

public void setNode(final NodeInfo node) {
this.node = node;
}

public void setAclEntries(final List<AccessControlInfo> aclEntries) {
this.aclEntries = aclEntries;
}

public SaveNodeResponse() {
}

public SaveNodeResponse() {}
}
12 changes: 11 additions & 1 deletion src/main/java/quanta/service/AclService.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,21 @@ public AddPrivilegeResponse addPrivilege(AddPrivilegeRequest req) {
boolean success = true;

for (String principal : req.getPrincipals()) {
principal = XString.stripIfStartsWith(principal, "@");
if (!addPrivilege(null, node, principal, null, req.getPrivileges(), res)) {
success = false;
}
}

// If removing encryption, remove it from all the ACL entries too.
String encKey = node.getStr(NodeProp.ENC_KEY);
if (encKey == null) {
svc_mongoUtil.removeAllEncryptionKeys(node);
}
/* if node is currently encrypted */
else {
res.setAclEntries(svc_auth.getAclEntries(node));
}

res.setCode(success ? 200 : HttpServletResponse.SC_EXPECTATION_FAILED);
return res;
}
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/quanta/service/NodeEditService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package quanta.service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.StringTokenizer;
Expand Down Expand Up @@ -99,6 +98,7 @@ public SaveNodeResponse saveNode(SaveNodeRequest req) {
NodeInfo nodeInfo = req.getNode();
String nodeId = nodeInfo.getId();
SubNode node = svc_mongoRead.getNode(nodeId);

svc_auth.ownerAuth(node);
svc_mongoRead.forceCheckHasChildren(node);

Expand Down Expand Up @@ -182,7 +182,9 @@ else if (nodeInfo.getName() != null && nodeInfo.getName().length() > 0
String encKey = node.getStr(NodeProp.ENC_KEY);
if (encKey == null) {
svc_mongoUtil.removeAllEncryptionKeys(node);
} else /* if node is currently encrypted */ {
}
/* if node is currently encrypted */
else {
res.setAclEntries(svc_auth.getAclEntries(node));
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/public/src/Crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class Crypto {
// check if we have decrypted this content before
clearText = this.clearTextCache.get(node.content);

// if now, then decrypt it now
// if not, then decrypt it now
if (!clearText) {
const cipherText = node.content.substring(J.Constant.ENC_TAG.length);
const cipherKey = S.props.getCryptoKey(node);
Expand Down Expand Up @@ -558,7 +558,7 @@ export class Crypto {
}

/**
* Does a simplel symmetric encryption of the data using the given key, and if the key
* Does a simple symmetric encryption of the data using the given key, and if the key
* is not provided assumes the STORE_SYMKEY
*/
async symEncryptString(key: CryptoKey, data: string): Promise<string> {
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/public/src/Edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,10 @@ export class Edit {

// get the asym-encrypted sym Key to this node (decryptable by owner of node only, which is us)
const cipherKey = S.props.getPropStr(J.NodeProp.ENC_KEY, node);
if (!cipherKey) {
console.warn("No cipher key found on node.");
return;
}

// get this broswer's private key from browser storage
const privateKey: CryptoKey = await S.crypto.getPrivateEncKey();
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/public/src/JavaIntf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ export interface AddFriendResponse extends ResponseBase {
export interface AddPrivilegeResponse extends ResponseBase {
principalPublicKey: string;
principalNodeId: string;
aclEntries: AccessControlInfo[];
}

export interface AskSubGraphResponse extends ResponseBase {
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/public/src/PluginMgr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class PluginMgr {
this.addType(ordinal++, new AIQueryType());
this.addType(ordinal++, new CalcType());

// todo-0: not activating this yet. We may end up just using "composible System Prompts" for this instead.
// todo-1: not activating this yet. We may end up just using "composible System Prompts" for this instead.
// this.addType(ordinal++, new AIAgentType());
this.addType(ordinal++, new RepoRootType());
this.addType(ordinal++, new AccountType());
Expand Down
71 changes: 8 additions & 63 deletions src/main/resources/public/src/dlg/ManageCryptoKeysDlg.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Comp } from "../comp/base/Comp";
import { Button } from "../comp/core/Button";
import { ButtonBar } from "../comp/core/ButtonBar";
import { Selection } from "../comp/core/Selection";
import { TextContent } from "../comp/core/TextContent";
import { DialogBase } from "../DialogBase";
import { S } from "../Singletons";
Expand All @@ -10,37 +9,22 @@ import { ConfirmDlg } from "./ConfirmDlg";
import { ImportCryptoKeyDlg } from "./ImportCryptoKeyDlg";

interface LS { // Local State
keyType?: string;
keyJson?: string;
}

export class ManageCryptoKeysDlg extends DialogBase {

constructor() {
super("Security Keys");
this.mergeState({ keyType: "sig" });
super("Encryption Keys");
}

renderDlg(): Comp[] {
const state: LS = this.getState<LS>();
return [
new Selection(null, "Select Key", [
{ key: "sig", val: this.getKeyTypeName("sig") }, // STORE_SIGKEY
{ key: "asym", val: this.getKeyTypeName("asym") } // STORE_ASYMKEY

// currently not sing SYMKEY for anything.
// { key: "sym", val: this.getKeyTypeName("sym") } // STORE_SYMKEY
], "selectKeyTypeDropDown", {
setValue: (val: string) => {
this.mergeState<LS>({ keyType: val, keyJson: "Loading..." });
setTimeout(() => { this.preLoad(); }, 500);
},
getValue: (): string => this.getState<LS>().keyType
}),
new ButtonBar([
new Button("New Key", this._newKey),
// new Button("Remove Key", this.removeKey),
state.keyType !== "sym" ? new Button("Publish Public Key", this._publishKey) : null,
new Button("Publish Public Key", this._publishKey),
new Button("Import Key", this._importKey)
], "mb-3"),
new TextContent(state.keyJson, "cryptoKeyTextContent", true),
Expand All @@ -60,19 +44,7 @@ export class ManageCryptoKeysDlg extends DialogBase {
"-danger", Tailwind.alertDanger);
await dlg.open();
if (!dlg.yes) return;
const state: LS = this.getState<LS>();
switch (state.keyType) {
case "sig":
await S.localDB.removeByKey(S.crypto.STORE_SIGKEY);
break;
case "asym":
await S.localDB.removeByKey(S.crypto.STORE_ASYMKEY);
break;
case "sym":
await S.localDB.removeByKey(S.crypto.STORE_SYMKEY);
break;
default: break;
}
await S.localDB.removeByKey(S.crypto.STORE_ASYMKEY);
this.preLoad();
}

Expand All @@ -81,8 +53,7 @@ export class ManageCryptoKeysDlg extends DialogBase {
"-danger", Tailwind.alertDanger);
await dlg.open();
if (!dlg.yes) return;
const state: LS = this.getState<LS>();
await S.crypto.initKeys(S.quanta.userName, true, true, true, state.keyType);
await S.crypto.initKeys(S.quanta.userName, true, true, true, "asym");
this.preLoad();
}

Expand All @@ -91,44 +62,18 @@ export class ManageCryptoKeysDlg extends DialogBase {
"-danger", Tailwind.alertDanger);
await dlg.open();
if (!dlg.yes) return;
const state: LS = this.getState<LS>();
S.crypto.initKeys(S.quanta.userName, false, true, false, state.keyType);
S.crypto.initKeys(S.quanta.userName, false, true, false, "asym");
}

_importKey = async (): Promise<void> => {
const state: LS = this.getState<LS>();
const dlg = new ImportCryptoKeyDlg(state.keyType, this.getKeyTypeName(state.keyType));
const dlg = new ImportCryptoKeyDlg("asym", "Asymmetric Key");
await dlg.open();
this.preLoad();
}

getKeyTypeName(abbrev: string): string {
switch (abbrev) {
case "sig": return "Signature Key";
case "asym": return "Asymmetric Key";
case "sym": return "Symmetric Key";
default: break;
}
}

override async preLoad(): Promise<void> {
const state: LS = this.getState<LS>();
let keyJson: string = null;
switch (state.keyType) {
case "sig":
keyJson = await S.crypto.exportSigKeys();
this.mergeState<LS>({ keyJson });
break;
case "asym":
keyJson = await S.crypto.exportAsymKeys();
this.mergeState<LS>({ keyJson });
break;
case "sym":
keyJson = await S.crypto.exportSymKey();
this.mergeState<LS>({ keyJson });
break;
default:
this.mergeState<LS>({ keyJson: "" });
}
keyJson = await S.crypto.exportAsymKeys();
this.mergeState<LS>({ keyJson });
}
}
3 changes: 2 additions & 1 deletion src/main/resources/public/src/dlg/SharingDlg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ export class SharingDlg extends DialogBase {

async shareImmediate(names: string[]) {
const ast = getAs();
await S.rpcUtil.rpc<J.AddPrivilegeRequest, J.AddPrivilegeResponse>("addPrivilege", {
const res = await S.rpcUtil.rpc<J.AddPrivilegeRequest, J.AddPrivilegeResponse>("addPrivilege", {
nodeId: ast.editNode.id,
principals: names,
privileges: [J.PrivilegeType.READ, J.PrivilegeType.WRITE]
});
await S.edit.distributeKeys(ast.editNode, res.aclEntries);
this.reload();
}

Expand Down

0 comments on commit 6bd92ca

Please sign in to comment.