Update public source
This commit is contained in:
@@ -41,6 +41,27 @@ class MethodsRepository:
|
||||
),
|
||||
)
|
||||
|
||||
def update_method(
|
||||
self,
|
||||
method_id: str,
|
||||
name: str,
|
||||
description: str | None = None,
|
||||
) -> None:
|
||||
self._conn.execute(
|
||||
"""
|
||||
UPDATE crack_methods
|
||||
SET
|
||||
name = ?,
|
||||
description = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
(
|
||||
name,
|
||||
description,
|
||||
method_id,
|
||||
),
|
||||
)
|
||||
|
||||
def get_method(self, method_id: str) -> sqlite3.Row | None:
|
||||
row = self._conn.execute(
|
||||
"""
|
||||
@@ -99,6 +120,32 @@ class MethodsRepository:
|
||||
),
|
||||
)
|
||||
|
||||
def update_method_version(
|
||||
self,
|
||||
method_id: str,
|
||||
version: int,
|
||||
definition: dict[str, Any],
|
||||
) -> None:
|
||||
self._conn.execute(
|
||||
"""
|
||||
UPDATE crack_method_versions
|
||||
SET
|
||||
definition_json = ?
|
||||
WHERE method_id = ?
|
||||
AND version = ?
|
||||
""",
|
||||
(
|
||||
json.dumps(
|
||||
definition,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
sort_keys=True,
|
||||
),
|
||||
method_id,
|
||||
version,
|
||||
),
|
||||
)
|
||||
|
||||
def get_method_version(
|
||||
self,
|
||||
method_id: str,
|
||||
|
||||
@@ -42,6 +42,37 @@ class MethodService:
|
||||
|
||||
return method_id
|
||||
|
||||
def update_method(
|
||||
self,
|
||||
method_id: str,
|
||||
name: str,
|
||||
description: str | None = None,
|
||||
) -> None:
|
||||
if not method_id:
|
||||
raise MethodServiceError(
|
||||
"Method ID must not be empty"
|
||||
)
|
||||
|
||||
if not name or not name.strip():
|
||||
raise MethodServiceError(
|
||||
"Method name must not be empty"
|
||||
)
|
||||
|
||||
if self._methods.get_method(method_id) is None:
|
||||
raise MethodServiceError(
|
||||
"Method not found"
|
||||
)
|
||||
|
||||
self._methods.update_method(
|
||||
method_id=method_id,
|
||||
name=name.strip(),
|
||||
description=(
|
||||
description.strip()
|
||||
if description
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
||||
def create_method_version(
|
||||
self,
|
||||
method_id: str,
|
||||
@@ -64,6 +95,39 @@ class MethodService:
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
def update_method_version(
|
||||
self,
|
||||
method_id: str,
|
||||
version: int,
|
||||
definition: dict[str, Any],
|
||||
) -> None:
|
||||
if version <= 0:
|
||||
raise MethodServiceError(
|
||||
"Method version must be greater than zero"
|
||||
)
|
||||
|
||||
try:
|
||||
validate_method_definition(definition)
|
||||
except MethodDefinitionError as exc:
|
||||
raise MethodServiceError(str(exc)) from exc
|
||||
|
||||
if (
|
||||
self._methods.get_method_version(
|
||||
method_id,
|
||||
version,
|
||||
)
|
||||
is None
|
||||
):
|
||||
raise MethodServiceError(
|
||||
"Method version not found"
|
||||
)
|
||||
|
||||
self._methods.update_method_version(
|
||||
method_id=method_id,
|
||||
version=version,
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
def create_next_method_version(
|
||||
self,
|
||||
method_id: str,
|
||||
|
||||
@@ -128,6 +128,106 @@ def methods_page():
|
||||
methods=method_rows,
|
||||
)
|
||||
|
||||
@cracklab_bp.route(
|
||||
"/methods/<method_id>/edit/",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
def method_edit_page(method_id: str):
|
||||
error = None
|
||||
|
||||
conn = connect_database()
|
||||
|
||||
try:
|
||||
service = MethodService(conn)
|
||||
|
||||
method = service.get_method(method_id)
|
||||
|
||||
if method is None:
|
||||
raise MethodServiceError(
|
||||
"Метод не найден."
|
||||
)
|
||||
|
||||
latest = service.get_latest_method_version(
|
||||
method_id,
|
||||
)
|
||||
|
||||
if latest is None:
|
||||
raise MethodServiceError(
|
||||
"У метода отсутствует версия."
|
||||
)
|
||||
|
||||
if request.method == "POST":
|
||||
try:
|
||||
name = request.form.get(
|
||||
"name",
|
||||
"",
|
||||
).strip()
|
||||
|
||||
description = request.form.get(
|
||||
"description",
|
||||
"",
|
||||
).strip()
|
||||
|
||||
hash_mode_raw = request.form.get(
|
||||
"hash_mode",
|
||||
"22000",
|
||||
).strip()
|
||||
|
||||
if not name:
|
||||
raise MethodServiceError(
|
||||
"Введите название метода."
|
||||
)
|
||||
|
||||
if hash_mode_raw != "22000":
|
||||
raise MethodServiceError(
|
||||
"Сейчас поддерживается только hash mode 22000."
|
||||
)
|
||||
|
||||
steps = _build_steps_from_form()
|
||||
|
||||
definition = {
|
||||
"schema_version": 1,
|
||||
"hash_mode": 22000,
|
||||
"steps": steps,
|
||||
}
|
||||
|
||||
with conn:
|
||||
service.update_method(
|
||||
method_id=method_id,
|
||||
name=name,
|
||||
description=description or None,
|
||||
)
|
||||
|
||||
service.update_method_version(
|
||||
method_id=method_id,
|
||||
version=latest["version"],
|
||||
definition=definition,
|
||||
)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"cracklab.methods_page",
|
||||
)
|
||||
)
|
||||
|
||||
except (MethodServiceError, ValueError) as exc:
|
||||
error = str(exc)
|
||||
|
||||
definition = json.loads(
|
||||
latest["definition_json"]
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"method_create.html",
|
||||
active_page="methods",
|
||||
error=error,
|
||||
method=method,
|
||||
definition=definition,
|
||||
edit_mode=True,
|
||||
)
|
||||
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@cracklab_bp.route("/methods/create/", methods=["GET", "POST"])
|
||||
def method_create_page():
|
||||
|
||||
@@ -322,11 +322,19 @@
|
||||
|
||||
<div>
|
||||
<h1 class="page-title">
|
||||
Создание метода
|
||||
{% if edit_mode %}
|
||||
Редактирование метода
|
||||
{% else %}
|
||||
Создание метода
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
<div class="page-description">
|
||||
Новый рабочий метод будет сохранён в базе данных.
|
||||
{% if edit_mode %}
|
||||
Изменения будут сохранены в текущей версии метода.
|
||||
{% else %}
|
||||
Новый рабочий метод будет сохранён в базе данных.
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -366,6 +374,7 @@
|
||||
type="text"
|
||||
required
|
||||
autocomplete="off"
|
||||
value="{{ method.name if method else '' }}"
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -405,7 +414,7 @@
|
||||
id="description"
|
||||
name="description"
|
||||
placeholder="Например: имя + четыре цифры"
|
||||
></textarea>
|
||||
>{{ method.description if method and method.description else '' }}</textarea>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -498,7 +507,11 @@
|
||||
type="submit"
|
||||
class="methods-button"
|
||||
>
|
||||
Сохранить метод
|
||||
{% if edit_mode %}
|
||||
Сохранить изменения
|
||||
{% else %}
|
||||
Сохранить метод
|
||||
{% endif %}
|
||||
</button>
|
||||
|
||||
<a
|
||||
@@ -516,6 +529,12 @@
|
||||
|
||||
|
||||
<script>
|
||||
const editMode =
|
||||
{{ "true" if edit_mode else "false" }};
|
||||
|
||||
const initialSteps =
|
||||
{{ definition.get("steps", []) | tojson if definition else "[]" }};
|
||||
|
||||
const stepsContainer =
|
||||
document.getElementById("steps");
|
||||
|
||||
@@ -567,7 +586,16 @@
|
||||
name,
|
||||
label,
|
||||
help,
|
||||
value = "",
|
||||
) {
|
||||
const displayValue =
|
||||
value
|
||||
? value.replace(
|
||||
/^wordlist:/,
|
||||
"",
|
||||
)
|
||||
: "";
|
||||
|
||||
return `
|
||||
<div class="method-field">
|
||||
<label>
|
||||
@@ -580,6 +608,7 @@
|
||||
placeholder="insidepro-wpa"
|
||||
required
|
||||
autocomplete="off"
|
||||
value="${escapeHtml(displayValue)}"
|
||||
>
|
||||
|
||||
<div class="method-help">
|
||||
@@ -590,7 +619,10 @@
|
||||
}
|
||||
|
||||
|
||||
function maskField(name) {
|
||||
function maskField(
|
||||
name,
|
||||
value = "",
|
||||
) {
|
||||
return `
|
||||
<div class="method-field">
|
||||
<label>
|
||||
@@ -603,6 +635,7 @@
|
||||
placeholder="?d?d?d?d?d?d?d?d"
|
||||
required
|
||||
autocomplete="off"
|
||||
value="${escapeHtml(value)}"
|
||||
>
|
||||
|
||||
<div class="method-help">
|
||||
@@ -1004,6 +1037,7 @@
|
||||
function renderStepFields(
|
||||
stepNumber,
|
||||
mode,
|
||||
stepData = null,
|
||||
) {
|
||||
const prefix =
|
||||
`step_${stepNumber}`;
|
||||
@@ -1016,6 +1050,7 @@
|
||||
`${prefix}_dictionary`,
|
||||
"Словарь",
|
||||
"Укажите логическое имя wordlist, которое настроено на клиенте.",
|
||||
stepData?.dictionary?.resource || "",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1025,12 +1060,14 @@
|
||||
`${prefix}_dictionary_1`,
|
||||
"Словарь — Left",
|
||||
"Этот словарь образует левую часть кандидата.",
|
||||
stepData?.dictionaries?.[0]?.resource || "",
|
||||
);
|
||||
|
||||
html += dictionaryField(
|
||||
`${prefix}_dictionary_2`,
|
||||
"Словарь — Right",
|
||||
"Этот словарь образует правую часть кандидата.",
|
||||
stepData?.dictionaries?.[1]?.resource || "",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1038,6 +1075,7 @@
|
||||
if (mode === "3") {
|
||||
html += maskField(
|
||||
`${prefix}_mask`,
|
||||
stepData?.mask || "",
|
||||
);
|
||||
|
||||
html += `
|
||||
@@ -1058,10 +1096,12 @@
|
||||
`${prefix}_dictionary`,
|
||||
"Словарь — Left",
|
||||
"Словарь образует левую часть кандидата.",
|
||||
stepData?.dictionary?.resource || "",
|
||||
);
|
||||
|
||||
html += maskField(
|
||||
`${prefix}_mask`,
|
||||
stepData?.mask || "",
|
||||
);
|
||||
|
||||
html += `
|
||||
@@ -1079,6 +1119,7 @@
|
||||
if (mode === "7") {
|
||||
html += maskField(
|
||||
`${prefix}_mask`,
|
||||
stepData?.mask || "",
|
||||
);
|
||||
|
||||
html += `
|
||||
@@ -1095,6 +1136,7 @@
|
||||
`${prefix}_dictionary`,
|
||||
"Словарь — Right",
|
||||
"Словарь образует правую часть кандидата.",
|
||||
stepData?.dictionary?.resource || "",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1304,7 +1346,9 @@
|
||||
}
|
||||
|
||||
|
||||
function addStep() {
|
||||
function addStep(
|
||||
initialStep = null,
|
||||
) {
|
||||
stepCount += 1;
|
||||
|
||||
const currentStepNumber =
|
||||
@@ -1440,6 +1484,27 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (initialStep) {
|
||||
modeSelect.value =
|
||||
String(
|
||||
initialStep.attack_mode,
|
||||
);
|
||||
|
||||
workloadProfile =
|
||||
step.querySelector(
|
||||
"[data-workload-profile]",
|
||||
);
|
||||
|
||||
if (
|
||||
workloadProfile &&
|
||||
initialStep.workload_profile
|
||||
) {
|
||||
workloadProfile.value =
|
||||
String(
|
||||
initialStep.workload_profile,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
stepsContainer.appendChild(
|
||||
step,
|
||||
@@ -1462,7 +1527,9 @@
|
||||
);
|
||||
|
||||
|
||||
function updateStep() {
|
||||
function updateStep(
|
||||
initialData = null,
|
||||
) {
|
||||
const mode =
|
||||
modeSelect.value;
|
||||
|
||||
@@ -1475,6 +1542,7 @@
|
||||
renderStepFields(
|
||||
number,
|
||||
mode,
|
||||
initialData,
|
||||
);
|
||||
|
||||
modeLabel.textContent =
|
||||
@@ -1498,7 +1566,9 @@
|
||||
|
||||
modeSelect.addEventListener(
|
||||
"change",
|
||||
updateStep,
|
||||
() => {
|
||||
updateStep();
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1521,13 +1591,38 @@
|
||||
);
|
||||
|
||||
|
||||
updateStep();
|
||||
if (initialStep) {
|
||||
modeSelect.value =
|
||||
String(
|
||||
initialStep.attack_mode,
|
||||
);
|
||||
|
||||
const workloadProfile =
|
||||
step.querySelector(
|
||||
"[data-workload-profile]",
|
||||
);
|
||||
|
||||
if (
|
||||
workloadProfile &&
|
||||
initialStep.workload_profile
|
||||
) {
|
||||
workloadProfile.value =
|
||||
String(
|
||||
initialStep.workload_profile,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updateStep(
|
||||
initialStep,
|
||||
);
|
||||
|
||||
|
||||
stepCountInput.value =
|
||||
stepCount;
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
|
||||
function rebuildStepNumbers() {
|
||||
@@ -1693,8 +1788,15 @@
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
addStep();
|
||||
if (initialSteps.length > 0) {
|
||||
initialSteps.forEach(
|
||||
(step) => {
|
||||
addStep(step);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
addStep();
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -93,6 +93,13 @@
|
||||
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="methods-button-secondary"
|
||||
href="{{ url_for('cracklab.method_edit_page', method_id=method.id) }}"
|
||||
>
|
||||
Редактировать
|
||||
</a>
|
||||
|
||||
</article>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
Reference in New Issue
Block a user