Browse Source

Benutzer- und Gruppenrechte auf Masken mit Sichten einfacher gesteltet

master
Andre Knieschewski 5 hours ago
parent
commit
f937b38dec
  1. 11
      superx/WEB-INF/conf/edustore/db/install/conf/kern.xml
  2. 31
      superx/edit/kern/groupinfo_edit.jsp
  3. 49
      superx/edit/kern/groupinfo_edit_masken.inc
  4. 215
      superx/edit/kern/masken_sichtrechte_edit.jsp
  5. 262
      superx/edit/kern/sichtrechte_helper.inc
  6. 25
      superx/edit/kern/userinfo_edit_masken.inc

11
superx/WEB-INF/conf/edustore/db/install/conf/kern.xml

@ -11064,14 +11064,7 @@ Außerdem können Sie hier dem Benutzer Berechtigungen über Gruppen, Sachgebiet
</customfield> </customfield>
<customfield name="Masken" type="subform" multipart="false" <customfield name="Masken" type="include" path="/edit/kern/groupinfo_edit_masken.inc" />
autoUpdate="false" maxRows="*" table="group_masken_bez" parentField="tid"
childField="groupinfo_id" orderBy="" allowNew="true" mode="update_insert_delete">
<field-selection complete="false" />
<customfield name="groupinfo_id" type="hidden"
overrideValue="tid" />
<customfield name="maskeninfo_id" nullFieldValue="" />
</customfield>
<!--<customfield name="Institution" type="subform" multipart="false" autoUpdate="false" <!--<customfield name="Institution" type="subform" multipart="false" autoUpdate="false"
@ -12496,5 +12489,3 @@ mode="full">
</form> </form>
</dbforms> </dbforms>
</module> </module>

31
superx/edit/kern/groupinfo_edit.jsp

@ -151,36 +151,7 @@ dbConnectionName="<%= mandantenid %>" ><input type="hidden" name="${_csrf.parame
</db:dbform> </db:dbform>
</table> </table>
</td> </td>
</tr> </tr><%@ include file="/edit/kern/groupinfo_edit_masken.inc" %><tr>
<tr>
<td class="db_label">Masken</td><td>
<table class="subform">
<db:dbform orderBy="" childField="groupinfo_id" parentField="tid" tableName="group_masken_bez" maxRows="*" captionResource="true" followUp="/edit/kern/groupinfo_edit.jsp" autoUpdate="false" multipart="false" dbConnectionName="<%= mandantenid %>">
<db:header/>
<db:body allowNew="true">
<tr class="subform_tr"><db:textField hidden="true" fieldName="groupinfo_id" overrideValue="<%= tid%>"/>
<td align="left">
<div id="fld_maskeninfo_id">
<input onblur="updateSelect(this)" style="display:none" size="10" name="key_group_masken_bezmaskeninfo_id" class="schluesselfeld" type="text">
<db:select id="inp_group_masken_bezmaskeninfo_id" styleClass="clsInputStyle" readOnly="false" onChange="updateKeyfield(this)" fieldName="maskeninfo_id" customEntry=",,true"><% sql = "select tid, sp_trunc_string((tid || ' - ' || trim(name)),60) from maskeninfo order by sp_trunc_string((tid || ' - ' || trim(name)),60);"; %><db:queryData name="group_masken_bez_maskeninfo38" query="<%= sql %>" dbConnectionName="<%= mandantenid %>" /> </db:select>
</div>
</td><td class="naviBtn">
<db:updateButton styleClass="clsButtonStyle" style="height:18px" alt="button.update" src="../../images/save.svg" flavor="image" id="db_updateButton"/></td><td class="naviBtn">
<db:deleteButton confirmMessage="button.delete.confirm" styleClass="clsButtonStyle" style="height:18px" alt="button.delete" src="../../images/delete.svg" id="db_deleteButton" flavor="image"/></td>
</tr>
</db:body>
<db:footer>
<tr class="subform_tr">
<td><td class="naviBtn"><span connectId="new4" dojoType="tooltip">Neuen Datensatz erzeugen. Mit Mausklick auf diesen Button geht das Formular bzw. Unterformular in den Einf&uuml;gemodus, alle vorhandenen Datens&auml;tze werden ausgeblendet. Zum Einf&uuml;gen des neuen Datensatzes klicken Sie dann auf das H&auml;kchen-Symbol rechts neben bzw. unter mir.</span>
<db:navNewButton styleClass="clsButtonStyle" style="height:18px" showAlwaysInFooter="false" alt="button.nav.new" src="../../images/context_add.svg" id="new4" flavor="image"/></td><td class="naviBtn"><span connectId="ins4" dojoType="tooltip">Datensatz einf&uuml;gen. Wenn Sie einen neuen Datensatz erzeugt haben, k&ouml;nnen Sie diesen mit diesem Button speichern.</span>
<db:insertButton showAlways="false" style="height:18px" styleClass="clsButtonStyle" alt="Datensatz einf&uuml;gen" src="../../images/circle_tick.svg" id="ins4" flavor="image"/></td></td>
</tr>
</db:footer>
</db:dbform>
</table>
</td>
</tr>
<tr>
<td class="db_label">Sichten</td><td> <td class="db_label">Sichten</td><td>
<table class="subform"> <table class="subform">
<db:dbform orderBy="" childField="groupinfo_id" parentField="tid" tableName="group_sichten" maxRows="*" captionResource="true" followUp="/edit/kern/groupinfo_edit.jsp" autoUpdate="false" multipart="false" dbConnectionName="<%= mandantenid %>"> <db:dbform orderBy="" childField="groupinfo_id" parentField="tid" tableName="group_sichten" maxRows="*" captionResource="true" followUp="/edit/kern/groupinfo_edit.jsp" autoUpdate="false" multipart="false" dbConnectionName="<%= mandantenid %>">

49
superx/edit/kern/groupinfo_edit_masken.inc

@ -0,0 +1,49 @@
<%@ include file="/edit/kern/sichtrechte_helper.inc" %>
<%
/* Statusdaten fuer die Maskenzeilen ermitteln (Gruppe 0 ist gueltig). */
int srSubjectId = srInt(tid);
SRData srListData = null;
if (srSubjectId >= 0) {
try (Connection srCon = srConnection(mandantenid)) {
try { srCon.setReadOnly(true); } catch (SQLException srReadOnlyEx) {
application.log("Read-only fuer Sichtenpruefung nicht verfuegbar", srReadOnlyEx);
}
srListData = srLoad(srCon, "group", srSubjectId,
srAssignedMasks(srCon, "group", srSubjectId));
} catch (Exception srEx) {
application.log("SuperX Sichtrechte-Status: Analyse fehlgeschlagen", srEx);
}
}
%>
<tr>
<td class="db_label">Masken</td><td>
<table class="subform">
<db:dbform orderBy="" childField="groupinfo_id" parentField="tid" tableName="group_masken_bez" maxRows="*" captionResource="true" followUp="/edit/kern/groupinfo_edit.jsp" autoUpdate="false" multipart="false" dbConnectionName="<%= mandantenid %>">
<db:header/>
<db:body allowNew="true">
<tr class="subform_tr"><db:textField hidden="true" fieldName="groupinfo_id" overrideValue="<%= tid%>"/>
<td align="left">
<div id="fld_maskeninfo_id">
<input onblur="updateSelect(this)" style="display:none" size="10" name="key_group_masken_bezmaskeninfo_id" class="schluesselfeld" type="text">
<db:select id="inp_group_masken_bezmaskeninfo_id" styleClass="clsInputStyle" readOnly="false" onChange="updateKeyfield(this)" fieldName="maskeninfo_id" customEntry=",,true"><% sql = "select tid, sp_trunc_string((tid || ' - ' || trim(name)),60) from maskeninfo order by sp_trunc_string((tid || ' - ' || trim(name)),60);"; %><db:queryData name="group_masken_bez_maskeninfo38" query="<%= sql %>" dbConnectionName="<%= mandantenid %>" /> </db:select>
</div>
</td><td class="naviBtn">
<db:updateButton styleClass="clsButtonStyle" style="height:18px" alt="button.update" src="../../images/save.svg" flavor="image" id="db_updateButton"/></td><td class="naviBtn">
<db:deleteButton confirmMessage="button.delete.confirm" styleClass="clsButtonStyle" style="height:18px" alt="button.delete" src="../../images/delete.svg" id="db_deleteButton" flavor="image"/></td>
<td style="white-space:nowrap;vertical-align:middle"><% if (currentRow_group_masken_bez != null) {
int srMaskId=srInt(currentRow_group_masken_bez.get("maskeninfo_id"));
out.print(srInlineStatus(srListData,srMaskId,"group",srSubjectId));
} %></td>
</tr>
</db:body>
<db:footer>
<tr class="subform_tr">
<td><td class="naviBtn"><span connectId="new4" dojoType="tooltip">Neuen Datensatz erzeugen. Mit Mausklick auf diesen Button geht das Formular bzw. Unterformular in den Einf&uuml;gemodus, alle vorhandenen Datens&auml;tze werden ausgeblendet. Zum Einf&uuml;gen des neuen Datensatzes klicken Sie dann auf das H&auml;kchen-Symbol rechts neben bzw. unter mir.</span>
<db:navNewButton styleClass="clsButtonStyle" style="height:18px" showAlwaysInFooter="false" alt="button.nav.new" src="../../images/context_add.svg" id="new4" flavor="image"/></td><td class="naviBtn"><span connectId="ins4" dojoType="tooltip">Datensatz einf&uuml;gen. Wenn Sie einen neuen Datensatz erzeugt haben, k&ouml;nnen Sie diesen mit diesem Button speichern.</span>
<db:insertButton showAlways="false" style="height:18px" styleClass="clsButtonStyle" alt="Datensatz einf&uuml;gen" src="../../images/circle_tick.svg" id="ins4" flavor="image"/></td></td>
</tr>
</db:footer>
</db:dbform>
</table>
</td>
</tr>

215
superx/edit/kern/masken_sichtrechte_edit.jsp

@ -0,0 +1,215 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="/edit/kern/sichtrechte_helper.inc" %>
<%
if(request.getCharacterEncoding()==null) request.setCharacterEncoding("UTF-8");
Object userobject=request.getSession().getAttribute("UserID");
if(userobject==null) { %><jsp:forward page="/edit/not_authenticated.htm"/><% return; }
String userid=userobject.toString();
Object mandantobject=request.getSession().getAttribute("MandantenID");
String mandantenid=mandantobject==null?"":mandantobject.toString();
if("default".equals(mandantenid)) mandantenid="";
String typ=request.getParameter("typ");
String db_form_name="group".equals(typ)?"groupinfo":"userinfo";
String erlaubt="0";
%>
<%@ include file="/edit/check_authentication.inc" %>
<%
if("0".equals(erlaubt)) { %><jsp:forward page="/edit/not_authorized.htm"/><% return; }
int subjectId=srInt(request.getParameter("id"));
int maskId=srInt(request.getParameter("maskeninfo_id"));
if((!"group".equals(typ) && !"user".equals(typ)) || subjectId<0 || maskId<=0) {
response.sendError(400,"Ungueltige Parameter"); return;
}
SRData data;
String maskName="";
String error=null;
int saved=srInt(request.getParameter("saved"));
boolean writable=false;
try(Connection con=srConnection(mandantenid)) {
String subjectTable="group".equals(typ)?"groupinfo":"userinfo";
if(!srExists(con,subjectTable,"tid",subjectId) || !srMaskAssigned(con,typ,subjectId,maskId)) {
response.sendError(404,"Keine gespeicherte Maskenzuordnung vorhanden"); return;
}
maskName=srMaskName(con,maskId);
writable=srMayWrite(con,srInt(userid),db_form_name);
if("POST".equalsIgnoreCase(request.getMethod()) && !writable) {
response.sendError(403,"Keine Schreibberechtigung für dieses DBForm"); return;
}
try { con.setReadOnly(true); } catch(SQLException e) { application.log("Read-only für Sichtenprüfung nicht verfügbar",e); }
data=srLoad(con,typ,subjectId,Collections.singleton(maskId));
try { con.setReadOnly(false); } catch(SQLException e) { application.log("DB-Verbindung kann nicht auf schreibbar umgestellt werden",e); throw e; }
if("POST".equalsIgnoreCase(request.getMethod())) {
Set<String> allowedArts=new HashSet<String>();
Map<String,SRView> allowedViews=new HashMap<String,SRView>();
for(SRField f:data.fields.get(maskId)) for(SRView v:f.views) {
allowedArts.add(v.art); allowedViews.put(v.intern,v);
}
Set<String> newArts=new LinkedHashSet<String>();
Set<String> newViews=new LinkedHashSet<String>();
String[] postedArts=request.getParameterValues("art");
String[] postedViews=request.getParameterValues("sicht");
boolean valid=true;
if(postedArts!=null) for(String v:postedArts) {
if(!allowedArts.contains(v)) { valid=false; break; }
if(!data.rights.arts.contains(v)) newArts.add(v);
}
if(postedViews!=null) for(String v:postedViews) {
SRView view=allowedViews.get(v);
if(view==null) { valid=false; break; }
if(!data.rights.allows(view) && !newArts.contains(view.art)) newViews.add(v);
}
if(!valid) {
response.setStatus(400);
error="Die übermittelte Auswahl enthält nicht erlaubte Sichtrechte.";
} else if(newArts.isEmpty() && newViews.isEmpty()) {
error="Es wurden keine neuen Sichtrechte ausgewählt.";
} else {
try {
int count=srSave(con,typ,subjectId,newArts,newViews);
response.sendRedirect(response.encodeRedirectURL("masken_sichtrechte_edit.jsp?typ="+typ+
"&id="+subjectId+"&maskeninfo_id="+maskId+"&saved="+count));
return;
} catch(SQLException saveError) {
application.log("SuperX Sichtrechte: Speicherung fehlgeschlagen",saveError);
error="Die Berechtigungen konnten nicht gespeichert werden. Bitte Serverprotokoll prüfen.";
}
}
}
} catch(Exception ex) {
application.log("SuperX Sichtrechte: Analyse fehlgeschlagen",ex);
response.sendError(500,"Sichtrechte konnten nicht geladen werden; Details im Serverprotokoll."); return;
}
String backUrl="group".equals(typ)?"groupinfo_edit.jsp?tid="+subjectId:"userinfo_edit.jsp?tid="+subjectId;
List<SRField> fields=data.fields.get(maskId);
if(fields==null) fields=Collections.emptyList();
%>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sichtrechte für Maske <%= maskId %></title>
<link href="/superx/style/superx.css" type="text/css" rel="stylesheet">
<link href="/superx/style/dbforms_40.css" type="text/css" rel="stylesheet">
<style>
.sr-wrap { max-width:1000px; padding:18px 26px 36px; font-family:Arial,Helvetica,sans-serif; color:#252a31; }
.sr-heading { margin:0 0 7px; font-size:24px; font-weight:500; }
.sr-sub { color:#54616d; margin:0 0 18px; font-size:14px; }
.sr-back { display:inline-block; margin-bottom:16px; }
.sr-field { margin:18px 0; border:1px solid #cbd6df; background:#fff; border-radius:5px; padding:16px 18px; }
.sr-field h2 { margin:0; font-size:18px; font-weight:600; }
.sr-field-head { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:10px; }
.sr-art { margin:13px 0 0; border:1px solid #dbe2e8; border-radius:4px; background:#f8fbfd; }
.sr-art-top { display:flex; align-items:center; flex-wrap:wrap; justify-content:space-between; gap:12px; padding:10px 14px; }
.sr-art-top button { border:1px solid #9daebb; background:#fff; border-radius:4px; padding:4px 9px; cursor:pointer; }
.sr-art-body { padding:5px 14px 12px; background:#fff; }
.sr-art-label { display:flex; align-items:center; gap:9px; }
.sr-view { margin:8px 0 8px 24px; display:block; }
.sr-pill { font-size:12px; padding:3px 8px; border-radius:4px; white-space:nowrap; }
.sr-ok { color:#126b38; background:#eaf6ed; }
.sr-missing { color:#a71d1d; background:#ffefef; }
.sr-unknown { color:#855800; background:#fff2d9; }
.sr-note { font-size:12px; color:#637180; margin-top:7px; }
.sr-alert { padding:12px 15px; margin:10px 0; background:#fff2d9; border-left:4px solid #b47b13; }
.sr-success { padding:12px 15px; margin:10px 0; background:#eaf6ed; border-left:4px solid #25854b; }
.sr-buttons { display:flex; flex-wrap:wrap; gap:10px; margin:22px 0; }
.sr-buttons button,.sr-buttons a { padding:8px 15px; border-radius:4px; border:1px solid #647e91; background:#f6f8fa; color:#1c3242; text-decoration:none; cursor:pointer; font-size:14px; }
.sr-buttons button.sr-save { background:#2079ac; border-color:#2079ac; color:#fff; font-weight:bold; }
.sr-buttons button:disabled { opacity:.6; cursor:not-allowed; }
</style>
<script>
function srToggleArt(checkbox) {
var id=checkbox.getAttribute('data-artid');
var views=document.querySelectorAll('input[data-parent-art="'+id+'"]');
for(var i=0;i<views.length;i++) {
views[i].disabled=checkbox.checked || views[i].getAttribute('data-already')==='1';
}
// Mehrfach vorkommende Sichtarten im selben Formular synchron halten.
var matches=document.querySelectorAll('input[data-artid]');
for(var j=0;j<matches.length;j++) {
if(matches[j]!==checkbox && matches[j].getAttribute('data-artid')===id) matches[j].checked=checkbox.checked;
}
}
function srExpand(button) {
var div=document.getElementById(button.getAttribute('aria-controls'));
div.hidden=!div.hidden;
button.setAttribute('aria-expanded',div.hidden?'false':'true');
button.textContent=div.hidden?'Sichten anzeigen ▾':'Sichten ausblenden ▴';
}
function srResetSelections() { window.setTimeout(function() {
var arts=document.querySelectorAll('input[data-artid]');
for(var i=0;i<arts.length;i++) srToggleArt(arts[i]);
},0); }
</script>
</head>
<body class="clsPageBody">
<main class="sr-wrap">
<a class="sr-back" href="<%= srHtml(backUrl) %>">&larr; Zurück zur <%= "group".equals(typ)?"Gruppen":"Benutzer" %>verwaltung</a>
<h1 class="sr-heading">Sichtrechte prüfen und ergänzen</h1>
<p class="sr-sub"><strong>Maske <%= maskId %> – <%= srHtml(maskName) %></strong><br>
<%= "group".equals(typ)?"Gruppe":"Benutzer" %> (TID <%= subjectId %>)</p>
<% if(!writable) { %><div class="sr-alert">Nur Leseberechtigung: Eine neue Rechtevergabe ist mit dieser Anmeldung nicht möglich.</div><% }
if(saved>0) { %><div class="sr-success"><%= saved %> neue Berechtigung(en) gespeichert. Der SuperX-Cache muss ggf. aktualisiert werden; betroffene Benutzer müssen sich anschließend neu anmelden.</div><% }
if(error!=null) { %><div class="sr-alert"><%= srHtml(error) %></div><% } %>
<p>Für jeden Filter werden die durch die Maskenrelation ermittelten Sichtarten angezeigt. Sie können eine ganze Sichtart oder darunter einzelne Sichten freigeben. Bereits wirksame Berechtigungen sind gekennzeichnet.</p>
<form method="post" action="masken_sichtrechte_edit.jsp?typ=<%= typ %>&amp;id=<%= subjectId %>&amp;maskeninfo_id=<%= maskId %>" onreset="srResetSelections()">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
<% if(fields.isEmpty()) { %><div class="sr-success">Diese Maske besitzt keine Sichtfilter (felderinfo.art = 12). Es werden keine zusätzlichen Sichtrechte benötigt.</div><% }
int fieldNum=0;
Map<String,String> domArtIds=new HashMap<String,String>();
for(SRField f:fields) {
String fieldStatus=srFieldStatus(f,data.rights);
String label="ok".equals(fieldStatus)?"✓ Sichtrechte ausreichend":"missing".equals(fieldStatus)?"✕ Sichtenrechte fehlen":"? Prüfung unvollständig";
%>
<section class="sr-field">
<div class="sr-field-head"><h2>Filter: <%= srHtml(f.name) %></h2>
<span class="sr-pill <%= "ok".equals(fieldStatus)?"sr-ok":"missing".equals(fieldStatus)?"sr-missing":"sr-unknown" %>"><%= label %></span></div>
<p class="sr-note">Feld-TID: <%= f.id %></p>
<% if(f.problem!=null) { %><div class="sr-alert"><%= srHtml(f.problem) %> Für diesen Filter können hier keine Rechte automatisch vorgeschlagen werden.</div><% }
else {
Map<String,List<SRView>> byArt=new LinkedHashMap<String,List<SRView>>();
for(SRView view:f.views) {
if(!byArt.containsKey(view.art)) byArt.put(view.art,new ArrayList<SRView>());
byArt.get(view.art).add(view);
}
int artNum=0;
for(Map.Entry<String,List<SRView>> art:byArt.entrySet()) {
boolean existingArt=data.rights.admin || data.rights.arts.contains(art.getKey());
String artDomId="art_"+(fieldNum)+"_"+(artNum++);
// Der data-artid-Wert ist HTML-escaped; DOM-Attribute sind eindeutige Indizes
// statt freier Sichtartnamen, damit Sonderzeichen die JavaScript-Auswahl nicht stören.
String commonId=domArtIds.get(art.getKey());
if(commonId==null) { commonId="artkey"+domArtIds.size(); domArtIds.put(art.getKey(),commonId); }
%>
<div class="sr-art">
<div class="sr-art-top">
<label class="sr-art-label"><input type="checkbox" name="art" value="<%= srHtml(art.getKey()) %>"
data-artid="<%= commonId %>" onchange="srToggleArt(this)" <%= existingArt?"checked disabled":"" %>>
<strong><%= srHtml(art.getKey()) %></strong> <span class="sr-note">Ganze Sichtart freigeben <%= existingArt?"(bereits berechtigt)":"" %></span></label>
<button type="button" aria-controls="<%= artDomId %>" aria-expanded="false" onclick="srExpand(this)">Sichten anzeigen ▾</button>
</div>
<div class="sr-art-body" id="<%= artDomId %>" hidden>
<span class="sr-note"><%= art.getValue().size() %> Sichten dieser Sichtart</span>
<% for(SRView view:art.getValue()) {
boolean existingView=data.rights.allows(view);
%>
<label class="sr-view"><input type="checkbox" name="sicht" value="<%= srHtml(view.intern) %>"
data-parent-art="<%= commonId %>" data-already="<%= existingView?"1":"0" %>"
<%= existingView?"checked disabled":"" %>>
<%= srHtml(view.name) %> <span class="sr-note">(<%= srHtml(view.intern) %>) <%= existingView?"– bereits berechtigt":"" %></span></label>
<% } %>
</div>
</div>
<% } } %>
</section>
<% fieldNum++; } %>
<div class="sr-buttons">
<button type="submit" class="sr-save" <%= fields.isEmpty() || !writable?"disabled":"" %>>Auswahl speichern</button>
<button type="reset">Auswahl zurücksetzen</button>
<a href="<%= srHtml(backUrl) %>">Zurück ohne Änderungen</a>
</div>
<p class="sr-note">Die Funktion ergänzt ausschließlich Rechte. Vorhandene Rechte werden nicht entfernt. Für das Entziehen verwenden Sie weiterhin die ursprüngliche Benutzer-/Gruppenverwaltung. Bei dynamischen Relationen (z.B. FreeMarker) erfolgt keine automatische Freigabe.</p>
</form>
</main>
</body>
</html>

262
superx/edit/kern/sichtrechte_helper.inc

@ -0,0 +1,262 @@
<%@ page import="java.sql.*,java.util.*,org.dbforms.config.DbFormsConfigRegistry" %>
<%!
/* Gemeinsame Ermittlung der Sichtrechte für userinfo_edit.jsp,
groupinfo_edit.jsp und masken_sichtrechte_edit.jsp.
Alle Methoden sind request-lokal nutzbar und enthalten keine globalen Daten/Caches. */
private static class SRView {
int id; String intern, art, name;
SRView(int i,String n,String a,String label) { id=i; intern=n; art=a; name=label; }
}
private static class SRField {
int id; String name, relation, problem;
List<SRView> views = new ArrayList<SRView>();
SRField(int i,String n,String r) { id=i; name=n; relation=r; }
}
private static class SRRights {
Set<String> arts = new HashSet<String>();
Set<String> views = new HashSet<String>();
boolean admin = false;
boolean allows(SRView v) { return admin || arts.contains(v.art) || views.contains(v.intern); }
}
private static class SRData {
Map<Integer,List<SRField>> fields = new LinkedHashMap<Integer,List<SRField>>();
SRRights rights = new SRRights();
Map<Integer,String> maskStatus = new HashMap<Integer,String>();
}
private static String srHtml(Object value) {
if (value==null) return "";
return String.valueOf(value).replace("&","&amp;").replace("<","&lt;")
.replace(">","&gt;").replace("\"","&quot;").replace("'","&#39;");
}
private static int srInt(Object value) {
if (value==null) return -1;
try { int n=Integer.parseInt(String.valueOf(value).trim()); return n>=0?n:-1; }
catch (Exception e) { return -1; }
}
private static String srTrim(String s) { return s==null?"":s.trim(); }
private static String srStatus(List<SRField> fields, SRRights rights) {
if (fields==null) return "unknown";
boolean unknown=false, missing=false;
for (SRField f:fields) {
if (f.problem!=null || f.views.isEmpty()) { unknown=true; continue; }
boolean has=false;
for (SRView view:f.views) if (rights.allows(view)) { has=true; break; }
if (!has) missing=true;
}
return missing?"missing":(unknown?"unknown":"ok");
}
private static String srFieldStatus(SRField f, SRRights rights) {
if (f.problem!=null || f.views.isEmpty()) return "unknown";
for (SRView v:f.views) if (rights.allows(v)) return "ok";
return "missing";
}
private static Connection srConnection(String mandant) throws Exception {
return DbFormsConfigRegistry.instance().lookup().getConnection(mandant);
}
private static boolean srExists(Connection con,String table,String column,int id) throws SQLException {
try(PreparedStatement ps=con.prepareStatement("select 1 from "+table+" where "+column+"=?")) {
ps.setInt(1,id); try(ResultSet rs=ps.executeQuery()) { return rs.next(); }
}
}
private static boolean srMaskAssigned(Connection con,String typ,int id,int mask) throws SQLException {
String table="group".equals(typ)?"group_masken_bez":"user_masken_bez";
String col="group".equals(typ)?"groupinfo_id":"userinfo_id";
try(PreparedStatement ps=con.prepareStatement("select 1 from "+table+" where "+col+"=? and maskeninfo_id=?")) {
ps.setInt(1,id); ps.setInt(2,mask);
try(ResultSet rs=ps.executeQuery()) { return rs.next(); }
}
}
private static Set<Integer> srAssignedMasks(Connection con,String typ,int id) throws SQLException {
Set<Integer> result=new LinkedHashSet<Integer>();
String table="group".equals(typ)?"group_masken_bez":"user_masken_bez";
String col="group".equals(typ)?"groupinfo_id":"userinfo_id";
try(PreparedStatement ps=con.prepareStatement("select maskeninfo_id from "+table+" where "+col+"=?")) {
ps.setInt(1,id); try(ResultSet rs=ps.executeQuery()) { while(rs.next()) result.add(rs.getInt(1)); }
}
return result;
}
private static void srAddRights(Connection con, Set<String> target, String sql, int id) throws SQLException {
try(PreparedStatement ps=con.prepareStatement(sql)) {
ps.setInt(1,id);
try(ResultSet rs=ps.executeQuery()) {
while(rs.next()) if(rs.getString(1)!=null) target.add(rs.getString(1).trim());
}
}
}
private static SRRights srLoadRights(Connection con,String typ,int id) throws SQLException {
SRRights r=new SRRights();
if("group".equals(typ)) {
srAddRights(con,r.views,"select sicht_name_intern from group_sichten where groupinfo_id=?",id);
srAddRights(con,r.arts,"select sichtart from group_sichtarten where groupinfo_id=?",id);
srAddRights(con,r.views,"select ss.sicht_name_intern from sachgeb_sichten ss join group_sachgeb_bez b on b.sachgebiete_id=ss.sachgebiete_id where b.groupinfo_id=?",id);
srAddRights(con,r.arts,"select ss.sichtart from sachgeb_sichtarten ss join group_sachgeb_bez b on b.sachgebiete_id=ss.sachgebiete_id where b.groupinfo_id=?",id);
} else {
try(PreparedStatement ps=con.prepareStatement("select administration from userinfo where tid=?")) {
ps.setInt(1,id);
try(ResultSet rs=ps.executeQuery()) { if(rs.next()) r.admin=rs.getInt(1)==1; }
}
srAddRights(con,r.views,"select sicht_name_intern from user_sichten where userinfo_id=?",id);
srAddRights(con,r.arts,"select sichtart from user_sichtarten where userinfo_id=?",id);
srAddRights(con,r.views,"select gs.sicht_name_intern from group_sichten gs join user_group_bez ug on ug.groupinfo_id=gs.groupinfo_id where ug.userinfo_id=?",id);
srAddRights(con,r.arts,"select gs.sichtart from group_sichtarten gs join user_group_bez ug on ug.groupinfo_id=gs.groupinfo_id where ug.userinfo_id=?",id);
srAddRights(con,r.views,"select ss.sicht_name_intern from sachgeb_sichten ss join user_sachgeb_bez b on b.sachgebiete_id=ss.sachgebiete_id where b.userinfo_id=?",id);
srAddRights(con,r.arts,"select ss.sichtart from sachgeb_sichtarten ss join user_sachgeb_bez b on b.sachgebiete_id=ss.sachgebiete_id where b.userinfo_id=?",id);
srAddRights(con,r.views,"select ss.sicht_name_intern from sachgeb_sichten ss join group_sachgeb_bez gb on gb.sachgebiete_id=ss.sachgebiete_id join user_group_bez ug on ug.groupinfo_id=gb.groupinfo_id where ug.userinfo_id=?",id);
srAddRights(con,r.arts,"select ss.sichtart from sachgeb_sichtarten ss join group_sachgeb_bez gb on gb.sachgebiete_id=ss.sachgebiete_id join user_group_bez ug on ug.groupinfo_id=gb.groupinfo_id where ug.userinfo_id=?",id);
}
return r;
}
/* Nur unparametrisierte SELECTs mit Sichten-Zugriff. Dynamische Relationen werden
nie automatisch ausgeführt und erhalten den Status "nicht prüfbar". */
private static String srNormalizeSql(String raw) {
String s=srTrim(raw);
if (!s.regionMatches(true,0,"<<SQL>>",0,7)) return null;
s=s.substring(7).trim();
if (s.contains("<#") || s.contains("</#") || s.contains("${") || s.contains("<<") ||
s.toLowerCase(Locale.ROOT).contains("--freemarker")) return null;
// SQL-Zeilenkommentare sind erlaubt (z.B. der MAN-Dummy-Sicht-Kommentar).
s=s.replaceAll("(?m)--[^\\r\\n]*", " ").trim();
if (s.endsWith(";")) s=s.substring(0,s.length()-1).trim();
if (s.contains(";") || !s.matches("(?is)^select\\s+.*") ||
!s.matches("(?is).*\\bfrom\\s+sichten\\b.*")) return null;
// Defensive Schranke, auch für SQL, das später erweitert wird.
if (s.matches("(?is).*(?:\\b(insert|update|delete|drop|alter|create|truncate|call|execute|grant|revoke)\\b).*")) return null;
return s;
}
private static Map<Integer,SRView> srAllViews(Connection con) throws SQLException {
Map<Integer,SRView> res=new LinkedHashMap<Integer,SRView>();
try(PreparedStatement ps=con.prepareStatement("select tid, name_intern, art, name from sichten order by art, name, tid")) {
try(ResultSet rs=ps.executeQuery()) {
while(rs.next()) {
int n=rs.getInt(1);
res.put(n,new SRView(n,srTrim(rs.getString(2)),srTrim(rs.getString(3)),srTrim(rs.getString(4))));
}
}
}
return res;
}
private static SRData srLoad(Connection con,String typ,int id, Set<Integer> maskIds) throws SQLException {
SRData data=new SRData();
data.rights=srLoadRights(con,typ,id);
if(maskIds==null || maskIds.isEmpty()) return data;
for(Integer m:maskIds) data.fields.put(m,new ArrayList<SRField>());
Map<Integer,SRView> allViews=srAllViews(con);
Map<String,List<SRView>> queryCache=new HashMap<String,List<SRView>>();
StringBuilder placeholders=new StringBuilder();
for(int i=0;i<maskIds.size();i++) { if(i>0) placeholders.append(','); placeholders.append('?'); }
String getFields="select b.maskeninfo_id, f.tid, f.name, f.relation " +
"from masken_felder_bez b join felderinfo f on f.tid=b.felderinfo_id " +
"where f.art=12 and b.maskeninfo_id in ("+placeholders+") order by b.maskeninfo_id, f.nummer, f.tid";
try(PreparedStatement ps=con.prepareStatement(getFields)) {
int n=1; for(Integer m:maskIds) ps.setInt(n++,m);
try(ResultSet rs=ps.executeQuery()) {
while(rs.next()) {
int mask=rs.getInt(1); SRField f=new SRField(rs.getInt(2),srTrim(rs.getString(3)),rs.getString(4));
List<SRField> ff=data.fields.get(mask); if(ff!=null) ff.add(f);
}
}
}
for(List<SRField> fields:data.fields.values()) for(SRField field:fields) {
String normalized=srNormalizeSql(field.relation);
if(normalized==null) { field.problem="Dynamische oder nicht unterstützte SQL-Relation (z.B. FreeMarker/Platzhalter)."; continue; }
if(!queryCache.containsKey(normalized)) {
try(PreparedStatement ps=con.prepareStatement(normalized)) {
ps.setMaxRows(5001); ps.setQueryTimeout(4);
List<SRView> found=new ArrayList<SRView>();
Set<Integer> seen=new HashSet<Integer>();
try(ResultSet rs=ps.executeQuery()) {
while(rs.next()) {
int tid=rs.getInt(1);
SRView view=allViews.get(tid);
if(!rs.wasNull() && view!=null && seen.add(tid)) found.add(view);
}
}
// Ein Trefferlimit darf nicht stillschweigend als vollständige Auswertung gelten.
queryCache.put(normalized,found.size()>5000?null:found);
} catch(SQLException ex) { queryCache.put(normalized,null); }
}
List<SRView> found=queryCache.get(normalized);
if(found==null) field.problem="SQL konnte nicht sicher ausgewertet werden.";
else if(found.isEmpty()) field.problem="SQL liefert derzeit keine vorhandenen Sichten.";
else field.views.addAll(found);
}
for(Map.Entry<Integer,List<SRField>> entry:data.fields.entrySet())
data.maskStatus.put(entry.getKey(),srStatus(entry.getValue(),data.rights));
return data;
}
private static String srInlineStatus(SRData data,int mask,String typ,int subjectId) {
if(mask<=0 || subjectId<0) return "";
String state=data==null?"unknown":data.maskStatus.get(mask);
if(state==null) state="unknown";
String status="ok".equals(state)?"&#10003;":"missing".equals(state)?"&#10007;":"?";
String title="ok".equals(state)?"Sichtrechte ausreichend":"missing".equals(state)?"Sichtenrechte fehlen":"Sichtrechte nicht vollständig prüfbar";
String color="ok".equals(state)?"#188342":"missing".equals(state)?"#ba2222":"#aa7200";
String link="masken_sichtrechte_edit.jsp?typ="+typ+"&amp;id="+subjectId+"&amp;maskeninfo_id="+mask;
return "<span title=\""+srHtml(title)+"\" style=\"margin-left:5px;white-space:nowrap;color:"+color+";font-size:12px;\"><b>"+status+"</b>" +
("ok".equals(state)?"":"&nbsp;"+srHtml(title))+"</span>"+
" <a href=\""+link+"\" title=\"Sichtrechte prüfen/anpassen (gespeicherte Maske)\" style=\"margin-left:5px;display:inline-block;vertical-align:middle\">"+
"<img src=\"../../images/pencil.svg\" alt=\"Sichtrechte anpassen\" style=\"width:15px;height:15px;border:0\"></a>";
}
/* Direktes JDBC-Schreiben muss Schreibrechte eigenständig prüfen:
check_authentication.inc allein gewährt auch lesenden DBForms-Berechtigten Zugang. */
private static boolean srMayWrite(Connection con,int actingUser,String formName) throws SQLException {
if (actingUser<=0) return false;
try(PreparedStatement ps=con.prepareStatement("select administration from userinfo where tid=?")) {
ps.setInt(1,actingUser);try(ResultSet rs=ps.executeQuery()) {
if(rs.next() && rs.getInt(1)==1) return true;
}
}
// Die SuperX-Administratoren-Gruppe besitzt generell Schreibrechte für DBForms.
try(PreparedStatement ps=con.prepareStatement("select 1 from user_group_bez ug join groupinfo g on g.tid=ug.groupinfo_id where ug.userinfo_id=? and trim(g.name)='Administratoren'")) {
ps.setInt(1,actingUser);try(ResultSet rs=ps.executeQuery()) { if(rs.next()) return true; }
}
// Delegierte Bearbeiter nur für die dem konkreten DBForm zugeordneten Admin-Sachgebiete.
String query="select 1 from db_forms df join sachgeb_dbform_bez b on b.dbform_id=df.tid " +
"join sachgebiete sg on sg.tid=b.sachgebiete_id " +
"where trim(df.name)=? and (trim(sg.name) like 'Administration%' or trim(sg.name) like 'Bearbeitung%') " +
"and (exists(select 1 from user_sachgeb_bez us where us.sachgebiete_id=sg.tid and us.userinfo_id=?) " +
"or exists(select 1 from user_group_bez ug join group_sachgeb_bez gs on gs.groupinfo_id=ug.groupinfo_id where ug.userinfo_id=? and gs.sachgebiete_id=sg.tid))";
try(PreparedStatement ps=con.prepareStatement(query)) {
ps.setString(1,formName);ps.setInt(2,actingUser);ps.setInt(3,actingUser);
try(ResultSet rs=ps.executeQuery()) { return rs.next(); }
}
}
private static String srMaskName(Connection con,int mask) throws SQLException {
try(PreparedStatement ps=con.prepareStatement("select name from maskeninfo where tid=?")) {
ps.setInt(1,mask);try(ResultSet rs=ps.executeQuery()) { return rs.next()?srTrim(rs.getString(1)):""; }
}
}
private static boolean srRightExists(Connection con,String table,String idColumn,String keyColumn,int id,String value) throws SQLException {
try(PreparedStatement ps=con.prepareStatement("select 1 from "+table+" where "+idColumn+"=? and "+keyColumn+"=?")) {
ps.setInt(1,id);ps.setString(2,value);
try(ResultSet rs=ps.executeQuery()) { return rs.next(); }
}
}
private static int srSave(Connection con,String typ,int id,Set<String> newArts,Set<String> newViews) throws SQLException {
String suffix="group".equals(typ)?"group":"user";
String idCol="group".equals(typ)?"groupinfo_id":"userinfo_id";
boolean oldAutoCommit=con.getAutoCommit(); int added=0;
try {
con.setAutoCommit(false);
for(String art:newArts) {
String table=suffix+"_sichtarten";
if(!srRightExists(con,table,idCol,"sichtart",id,art)) {
try(PreparedStatement ps=con.prepareStatement("insert into "+table+" ("+idCol+",sichtart) values (?,?)")) {
ps.setInt(1,id);ps.setString(2,art);added+=ps.executeUpdate();
}
}
}
for(String view:newViews) {
String table=suffix+"_sichten";
if(!srRightExists(con,table,idCol,"sicht_name_intern",id,view)) {
try(PreparedStatement ps=con.prepareStatement("insert into "+table+" ("+idCol+",sicht_name_intern) values (?,?)")) {
ps.setInt(1,id);ps.setString(2,view);added+=ps.executeUpdate();
}
}
}
con.commit();
return added;
} catch(SQLException e) { con.rollback(); throw e; }
finally { con.setAutoCommit(oldAutoCommit); }
}
%>

25
superx/edit/kern/userinfo_edit_masken.inc

@ -1,3 +1,20 @@
<%@ include file="/edit/kern/sichtrechte_helper.inc" %>
<%
/* Statusdaten fuer die Maskenzeilen ermitteln (Gruppe 0 ist gueltig). */
int srSubjectId = srInt(tid);
SRData srListData = null;
if (srSubjectId > 0) {
try (Connection srCon = srConnection(mandantenid)) {
try { srCon.setReadOnly(true); } catch (SQLException srReadOnlyEx) {
application.log("Read-only fuer Sichtenpruefung nicht verfuegbar", srReadOnlyEx);
}
srListData = srLoad(srCon, "user", srSubjectId,
srAssignedMasks(srCon, "user", srSubjectId));
} catch (Exception srEx) {
application.log("SuperX Sichtrechte-Status: Analyse fehlgeschlagen", srEx);
}
}
%>
<tr><td class="db_label">Masken</td> <tr><td class="db_label">Masken</td>
<td> <td>
@ -25,6 +42,10 @@ if (currentRow_user_masken_bez.get("external_entry")==null||currentRow_user_mask
<td class="naviBtn"> <td class="naviBtn">
<db:deleteButton confirmMessage="Wollen Sie diesen Eintrag wirklich l&ouml;schen?" styleClass="clsButtonStyle" style="height:18px" alt="button.delete" src="../../images/delete.svg" id="db_deleteButton" flavor="image"/> <db:deleteButton confirmMessage="Wollen Sie diesen Eintrag wirklich l&ouml;schen?" styleClass="clsButtonStyle" style="height:18px" alt="button.delete" src="../../images/delete.svg" id="db_deleteButton" flavor="image"/>
</td> </td>
<td style="white-space:nowrap;vertical-align:middle"><%
int srMaskId=srInt(currentRow_user_masken_bez.get("maskeninfo_id"));
out.print(srInlineStatus(srListData,srMaskId,"user",srSubjectId));
%></td>
</tr> </tr>
<% } else {%> <% } else {%>
@ -37,6 +58,10 @@ if (currentRow_user_masken_bez.get("external_entry")==null||currentRow_user_mask
</td> </td>
<td></td> <td></td>
<td></td> <td></td>
<td style="white-space:nowrap;vertical-align:middle"><%
int srMaskId=srInt(currentRow_user_masken_bez.get("maskeninfo_id"));
out.print(srInlineStatus(srListData,srMaskId,"user",srSubjectId));
%></td>
</tr> </tr>
<% } <% }

Loading…
Cancel
Save