mirror of
https://github.com/wneessen/apg-go.git
synced 2024-11-22 13:50:49 +01:00
Merge pull request #31 from wneessen/cmd_refactor
Separate CLI app from library functions
This commit is contained in:
commit
81f15c4ef2
18 changed files with 313 additions and 514 deletions
23
.cirrus.yml
Normal file
23
.cirrus.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
container:
|
||||||
|
image: golang:latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
GOPROXY: https://proxy.golang.org
|
||||||
|
|
||||||
|
lint_task:
|
||||||
|
name: GolangCI Lint
|
||||||
|
container:
|
||||||
|
image: golangci/golangci-lint:latest
|
||||||
|
run_script: golangci-lint run -v --timeout 5m0s --out-format json > lint-report.json
|
||||||
|
always:
|
||||||
|
golangci_artifacts:
|
||||||
|
path: lint-report.json
|
||||||
|
type: text/json
|
||||||
|
format: golangci
|
||||||
|
|
||||||
|
build_task:
|
||||||
|
modules_cache:
|
||||||
|
folder: $GOPATH/pkg/mod
|
||||||
|
get_script: go get github.com/wneessen/apg-go/cmd/apg
|
||||||
|
build_script: go build github.com/wneessen/apg-go/cmd/apg
|
||||||
|
test_script: go test github.com/wneessen/apg-go/cmd/apg
|
6
.github/workflows/go.yml
vendored
6
.github/workflows/go.yml
vendored
|
@ -17,10 +17,10 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15
|
go-version: 1.17
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -o apg -v .
|
run: go build -o apg -v github.com/wneessen/apg-go/cmd/apg
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -v .
|
run: go test -v github.com/wneessen/apg-go/cmd/apg
|
||||||
|
|
8
.idea/.gitignore
vendored
8
.idea/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
|
@ -2,7 +2,7 @@
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/apg.go.iml" filepath="$PROJECT_DIR$/.idea/apg.go.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/apg-go.iml" filepath="$PROJECT_DIR$/.idea/apg-go.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -1,187 +1,43 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="AutoImportSettings">
|
<component name="AutoImportSettings">
|
||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="ALL" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="fbb0c733-4aa1-4d27-87d5-c7276d8aa613" name="Default Changelist" comment="For some reason, the tests on GH fail">
|
<list default="true" id="e32960c0-29e5-4669-9fc2-ef12314486ce" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/apg-go.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/apg-go.iml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/apg_test.go" beforeDir="false" afterPath="$PROJECT_DIR$/apg_test.go" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/config/config.go" beforeDir="false" afterPath="$PROJECT_DIR$/config/config.go" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/config.go" beforeDir="false" afterPath="$PROJECT_DIR$/config.go" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="FileTemplateManagerImpl">
|
<component name="GOROOT" url="file://$PROJECT_DIR$/../../go1.17.1" />
|
||||||
<option name="RECENT_TEMPLATES">
|
|
||||||
<list>
|
|
||||||
<option value="Go File" />
|
|
||||||
<option value="Go Application" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="GOROOT" url="file://$PROJECT_DIR$/../../go1.16.2" />
|
|
||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
|
||||||
<map>
|
|
||||||
<entry key="$PROJECT_DIR$" value="main" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="GitSEFilterConfiguration">
|
|
||||||
<file-type-list>
|
|
||||||
<filtered-out-file-type name="LOCAL_BRANCH" />
|
|
||||||
<filtered-out-file-type name="REMOTE_BRANCH" />
|
|
||||||
<filtered-out-file-type name="TAG" />
|
|
||||||
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
|
|
||||||
</file-type-list>
|
|
||||||
</component>
|
|
||||||
<component name="GoLibraries">
|
<component name="GoLibraries">
|
||||||
<option name="indexEntireGoPath" value="false" />
|
<option name="indexEntireGoPath" value="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectId" id="1pxyItB9dnh8hLHpqlNK6NktVaL" />
|
<component name="ProjectId" id="1yX0zXHdpGPxqctARqJlfIL2eFe" />
|
||||||
<component name="ProjectLevelVcsManager">
|
|
||||||
<OptionsSetting value="false" id="Update" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectViewState">
|
<component name="ProjectViewState">
|
||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent">
|
<component name="PropertiesComponent">
|
||||||
<property name="DefaultGoTemplateProperty" value="Go Application" />
|
|
||||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
<property name="WebServerToolWindowFactoryState" value="true" />
|
||||||
<property name="configurable..is.expanded" value="false" />
|
<property name="configurable..is.expanded" value="false" />
|
||||||
<property name="configurable.GoLibrariesConfigurable.is.expanded" value="true" />
|
<property name="configurable.GoLibrariesConfigurable.is.expanded" value="true" />
|
||||||
|
<property name="go.formatter.settings.were.checked" value="true" />
|
||||||
<property name="go.import.settings.migrated" value="true" />
|
<property name="go.import.settings.migrated" value="true" />
|
||||||
|
<property name="go.modules.go.list.on.any.changes.was.set" value="true" />
|
||||||
<property name="go.sdk.automatically.set" value="true" />
|
<property name="go.sdk.automatically.set" value="true" />
|
||||||
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
|
|
||||||
<property name="last_opened_file_path" value="$USER_HOME$" />
|
<property name="last_opened_file_path" value="$USER_HOME$" />
|
||||||
<property name="settings.editor.selected.configurable" value="inlay.hints.go" />
|
<property name="settings.editor.selected.configurable" value="go.vgo" />
|
||||||
</component>
|
|
||||||
<component name="RecentsManager">
|
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
|
||||||
<recent name="C:\Users\Winni Neessen\go\src\apg.go\chars" />
|
|
||||||
<recent name="C:\Users\Winni Neessen\go\src\apg.go\lib" />
|
|
||||||
<recent name="C:\Users\Winni Neessen\go\src\apg.go\test" />
|
|
||||||
</key>
|
|
||||||
</component>
|
|
||||||
<component name="RunManager" selected="Go Build.Run Application (with newstyle params)">
|
|
||||||
<configuration name="Run Application (show help text)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="-h" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Run Application (show version string)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="-v" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Run Application (with excludes)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="-m 20 -x 20 -C -E abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!]" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Run Application (with newstyle params and spelling and multiple pws)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="-m 10 -x 10 -M SLNU -l -n 6" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Run Application (with newstyle params and spelling)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="-m 10 -x 10 -M SLNU -l" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Run Application (with newstyle params)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="-M slNU" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Run Application (with params)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="-m 20 -x 20 -C" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Run Application" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Benchmark Application" type="GoTestRunConfiguration" factoryName="Go Test">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<framework value="gobench" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="Test Application" type="GoTestRunConfiguration" factoryName="Go Test">
|
|
||||||
<module name="apg.go" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/wneessen/apg-go" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$" />
|
|
||||||
<framework value="gotest" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<list>
|
|
||||||
<item itemvalue="Go Build.Run Application" />
|
|
||||||
<item itemvalue="Go Build.Run Application (with params)" />
|
|
||||||
<item itemvalue="Go Build.Run Application (with excludes)" />
|
|
||||||
<item itemvalue="Go Build.Run Application (with newstyle params)" />
|
|
||||||
<item itemvalue="Go Build.Run Application (with newstyle params and spelling)" />
|
|
||||||
<item itemvalue="Go Build.Run Application (with newstyle params and spelling and multiple pws)" />
|
|
||||||
<item itemvalue="Go Build.Run Application (show help text)" />
|
|
||||||
<item itemvalue="Go Build.Run Application (show version string)" />
|
|
||||||
<item itemvalue="Go Test.Test Application" />
|
|
||||||
<item itemvalue="Go Test.Benchmark Application" />
|
|
||||||
</list>
|
|
||||||
</component>
|
</component>
|
||||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
@ -192,55 +48,13 @@
|
||||||
<map>
|
<map>
|
||||||
<entry key="MAIN">
|
<entry key="MAIN">
|
||||||
<value>
|
<value>
|
||||||
<State>
|
<State />
|
||||||
<option name="FILTERS">
|
|
||||||
<map>
|
|
||||||
<entry key="branch">
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<option value="main" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</State>
|
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="oldMeFiltersMigrated" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="VcsManagerConfiguration">
|
|
||||||
<MESSAGE value="Updated README and gitignore files" />
|
|
||||||
<MESSAGE value="v0.2.1: Support for more platforms added" />
|
|
||||||
<MESSAGE value="v0.2.3: More chars, cleanup and better test coverage - Added more special characters - Fixed issue with ` character - Added lots of tests - Moved character range generation into it's own function - Better error handling - A bit of code cleanup" />
|
|
||||||
<MESSAGE value="Split up functions into seperate files" />
|
|
||||||
<MESSAGE value="Added spelling of pws. Ready for v0.2.4" />
|
|
||||||
<MESSAGE value="Fixes in getCharRange() tests" />
|
|
||||||
<MESSAGE value="excludeChars are now escaped before using them as regExp" />
|
|
||||||
<MESSAGE value="Added sane defaults" />
|
|
||||||
<MESSAGE value="Updates README.md. We are ready for v0.2.5" />
|
|
||||||
<MESSAGE value="Break the switch/case" />
|
|
||||||
<MESSAGE value="v0.2.6 - converted to go module structure - Better logging - Better error handling - Removed Makefile since github takes care of building/releasing" />
|
|
||||||
<MESSAGE value="Tests cleanup. Used structs for tests instead of repeating code" />
|
|
||||||
<MESSAGE value="v0.2.7: Switched global config to it's own package" />
|
|
||||||
<MESSAGE value="Updated .gitignore" />
|
|
||||||
<MESSAGE value="Removed .idea from .gitignore" />
|
|
||||||
<MESSAGE value="Let's have centralized IDE config" />
|
|
||||||
<MESSAGE value="New DEV branch" />
|
|
||||||
<MESSAGE value="v0.2.9: Replaced standard go-help with custom usage text" />
|
|
||||||
<MESSAGE value="v0.3.0: Unified the naming convention" />
|
|
||||||
<MESSAGE value="v0.3.1: New password length behaviour To address issue #13, the password length behaviour of the original APG has been reproduced. Previously, when a minLength of 5 and a maxLength of 10 was given, apg-go se the pwLength to the preferred maxLength. With v0.3.1 it will choose a random length between minLength and maxLength instead, same as the original C-lang apg did. For this the minLength has been defaulted to a sane value of 12 (instead of the 8 of the original apg). The default for maxLength stayed at 20. Also the default number of generated passwords has been changed from 1 to 6, to replicate the behaviour of the original apg." />
|
|
||||||
<MESSAGE value="For some reason, the tests on GH fail" />
|
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="For some reason, the tests on GH fail" />
|
|
||||||
</component>
|
</component>
|
||||||
<component name="VgoProject">
|
<component name="VgoProject">
|
||||||
<integration-enabled>true</integration-enabled>
|
<integration-enabled>true</integration-enabled>
|
||||||
</component>
|
</component>
|
||||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
|
||||||
<SUITE FILE_PATH="coverage/apg_go$Test_Application.out" NAME="Test Application Coverage Results" MODIFIED="1616765230426" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="GoCoverage" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" />
|
|
||||||
<SUITE FILE_PATH="coverage/apg_go$TestGetRandNum_in_github_com_wneessen_apg_go__1_.out" NAME="TestGetRandNum in github.com/wneessen/apg.go (1) Coverage Results" MODIFIED="1616594443133" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="GoCoverage" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" />
|
|
||||||
<SUITE FILE_PATH="coverage/apg_go$BenchmarkGetRandNum_in_apg_go.out" NAME="BenchmarkGetRandNum in apg.go Coverage Results" MODIFIED="1616342745320" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="GoCoverage" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" />
|
|
||||||
</component>
|
|
||||||
</project>
|
</project>
|
|
@ -18,7 +18,7 @@ prepare() {
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${pkgname}-${pkgver}"
|
cd "${pkgname}-${pkgver}"
|
||||||
go build -ldflags="-s -w" -o build/${pkgname} ./...
|
go build -ldflags="-s -w" -o build/${pkgname} github.com/wneessen/apg-go/cmd/apg
|
||||||
}
|
}
|
||||||
|
|
||||||
package() {
|
package() {
|
||||||
|
|
|
@ -16,6 +16,6 @@ PERMIT_PACKAGE = Yes
|
||||||
MODULES = lang/go
|
MODULES = lang/go
|
||||||
MODGO_TYPE = bin
|
MODGO_TYPE = bin
|
||||||
|
|
||||||
ALL_TARGET = wneessen/apg-go
|
ALL_TARGET = wneessen/apg-go/cmd/apg
|
||||||
|
|
||||||
.include <bsd.port.mk>
|
.include <bsd.port.mk>
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package chars
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/wneessen/apg-go/config"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,13 +14,13 @@ const PwSpecialChars string = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||||
const PwNumbersHuman string = "23456789"
|
const PwNumbersHuman string = "23456789"
|
||||||
const PwNumbers string = "1234567890"
|
const PwNumbers string = "1234567890"
|
||||||
|
|
||||||
// Provide the range of available characters based on provided parameters
|
// GetRange provides the range of available characters based on configured parameters
|
||||||
func getCharRange(config *Config) string {
|
func GetRange(config *config.Config) string {
|
||||||
pwUpperChars := PwUpperChars
|
pwUpperChars := PwUpperChars
|
||||||
pwLowerChars := PwLowerChars
|
pwLowerChars := PwLowerChars
|
||||||
pwNumbers := PwNumbers
|
pwNumbers := PwNumbers
|
||||||
pwSpecialChars := PwSpecialChars
|
pwSpecialChars := PwSpecialChars
|
||||||
if config.humanReadable {
|
if config.HumanReadable {
|
||||||
pwUpperChars = PwUpperCharsHuman
|
pwUpperChars = PwUpperCharsHuman
|
||||||
pwLowerChars = PwLowerCharsHuman
|
pwLowerChars = PwLowerCharsHuman
|
||||||
pwNumbers = PwNumbersHuman
|
pwNumbers = PwNumbersHuman
|
||||||
|
@ -27,20 +28,20 @@ func getCharRange(config *Config) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var charRange string
|
var charRange string
|
||||||
if config.useLowerCase {
|
if config.UseLowerCase {
|
||||||
charRange = charRange + pwLowerChars
|
charRange = charRange + pwLowerChars
|
||||||
}
|
}
|
||||||
if config.useUpperCase {
|
if config.UseUpperCase {
|
||||||
charRange = charRange + pwUpperChars
|
charRange = charRange + pwUpperChars
|
||||||
}
|
}
|
||||||
if config.useNumber {
|
if config.UseNumber {
|
||||||
charRange = charRange + pwNumbers
|
charRange = charRange + pwNumbers
|
||||||
}
|
}
|
||||||
if config.useSpecial {
|
if config.UseSpecial {
|
||||||
charRange = charRange + pwSpecialChars
|
charRange = charRange + pwSpecialChars
|
||||||
}
|
}
|
||||||
if config.excludeChars != "" {
|
if config.ExcludeChars != "" {
|
||||||
regExp := regexp.MustCompile("[" + regexp.QuoteMeta(config.excludeChars) + "]")
|
regExp := regexp.MustCompile("[" + regexp.QuoteMeta(config.ExcludeChars) + "]")
|
||||||
charRange = regExp.ReplaceAllLiteralString(charRange, "")
|
charRange = regExp.ReplaceAllLiteralString(charRange, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,33 +3,17 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/wneessen/apg-go/chars"
|
||||||
|
"github.com/wneessen/apg-go/config"
|
||||||
|
"github.com/wneessen/apg-go/random"
|
||||||
|
"github.com/wneessen/apg-go/spelling"
|
||||||
|
"github.com/wneessen/go-hibp"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants
|
const VersionString string = "0.4.0-dev"
|
||||||
const DefaultMinLenght int = 12
|
|
||||||
const DefaultMaxLenght int = 20
|
|
||||||
const VersionString string = "0.3.2"
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
minPassLen int
|
|
||||||
maxPassLen int
|
|
||||||
numOfPass int
|
|
||||||
useComplex bool
|
|
||||||
useLowerCase bool
|
|
||||||
useUpperCase bool
|
|
||||||
useNumber bool
|
|
||||||
useSpecial bool
|
|
||||||
humanReadable bool
|
|
||||||
checkHibp bool
|
|
||||||
excludeChars string
|
|
||||||
newStyleModes string
|
|
||||||
spellPassword bool
|
|
||||||
ShowHelp bool
|
|
||||||
showVersion bool
|
|
||||||
outputMode int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Help text
|
// Help text
|
||||||
const usage = `apg-go // A "Automated Password Generator"-clone
|
const usage = `apg-go // A "Automated Password Generator"-clone
|
||||||
|
@ -58,37 +42,37 @@ Options:
|
||||||
|
|
||||||
// Main function that generated the passwords and returns them
|
// Main function that generated the passwords and returns them
|
||||||
func main() {
|
func main() {
|
||||||
// Log config
|
// Log configuration
|
||||||
log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile)
|
log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile)
|
||||||
|
|
||||||
// Read and parse flags
|
// Read and parse flags
|
||||||
flag.Usage = func() { _, _ = fmt.Fprintf(os.Stderr, "%s\n", usage) }
|
flag.Usage = func() { _, _ = fmt.Fprintf(os.Stderr, "%s\n", usage) }
|
||||||
var config = parseFlags()
|
var cfgObj = config.New()
|
||||||
|
|
||||||
// Show version and exit
|
// Show version and exit
|
||||||
if config.showVersion {
|
if cfgObj.ShowVersion {
|
||||||
_, _ = os.Stderr.WriteString(`apg-go // A "Automated Password Generator"-clone v` + VersionString + "\n")
|
_, _ = os.Stderr.WriteString(`apg-go // A "Automated Password Generator"-clone v` + VersionString + "\n")
|
||||||
_, _ = os.Stderr.WriteString("(C) 2021 by Winni Neessen\n")
|
_, _ = os.Stderr.WriteString("(C) 2021 by Winni Neessen\n")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set PW length and available characterset
|
// Set PW length and available characterset
|
||||||
charRange := getCharRange(&config)
|
charRange := chars.GetRange(&cfgObj)
|
||||||
|
|
||||||
// Generate passwords
|
// Generate passwords
|
||||||
for i := 1; i <= config.numOfPass; i++ {
|
for i := 1; i <= cfgObj.NumOfPass; i++ {
|
||||||
pwLength := getPwLengthFromParams(&config)
|
pwLength := config.GetPwLengthFromParams(&cfgObj)
|
||||||
pwString, err := getRandChar(&charRange, pwLength)
|
pwString, err := random.GetChar(&charRange, pwLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("getRandChar returned an error: %q\n", err)
|
log.Fatalf("error generating random character range: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch config.outputMode {
|
switch cfgObj.OutputMode {
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
spelledPw, err := spellPasswordString(pwString)
|
spelledPw, err := spelling.String(pwString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("spellPasswordString returned an error: %q\n", err.Error())
|
log.Fatalf("error spelling out password: %s\n", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("%v (%v)\n", pwString, spelledPw)
|
fmt.Printf("%v (%v)\n", pwString, spelledPw)
|
||||||
break
|
break
|
||||||
|
@ -100,12 +84,13 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.checkHibp {
|
if cfgObj.CheckHibp {
|
||||||
isPwned, err := checkHibp(pwString)
|
hc := hibp.New(hibp.WithHttpTimeout(time.Second*2), hibp.WithPwnedPadding())
|
||||||
|
pwnObj, _, err := hc.PwnedPassApi.CheckPassword(pwString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("unable to check HIBP database: %v", err)
|
log.Printf("unable to check HIBP database: %v", err)
|
||||||
}
|
}
|
||||||
if isPwned {
|
if pwnObj != nil && pwnObj.Count != 0 {
|
||||||
fmt.Print("^-- !!WARNING: The previously generated password was found in HIPB database. Do not use it!!\n")
|
fmt.Print("^-- !!WARNING: The previously generated password was found in HIPB database. Do not use it!!\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,15 +1,19 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/wneessen/apg-go/chars"
|
||||||
|
"github.com/wneessen/apg-go/config"
|
||||||
|
"github.com/wneessen/apg-go/random"
|
||||||
|
"github.com/wneessen/apg-go/spelling"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var config Config
|
var cfgObj config.Config
|
||||||
|
|
||||||
// Make sure the flags are initalized
|
// Make sure the flags are initalized
|
||||||
var _ = func() bool {
|
var _ = func() bool {
|
||||||
testing.Init()
|
testing.Init()
|
||||||
config = parseFlags()
|
cfgObj = config.New()
|
||||||
return true
|
return true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -30,7 +34,7 @@ func TestGetRandNum(t *testing.T) {
|
||||||
|
|
||||||
for _, testCase := range testTable {
|
for _, testCase := range testTable {
|
||||||
t.Run(testCase.testName, func(t *testing.T) {
|
t.Run(testCase.testName, func(t *testing.T) {
|
||||||
randNum, err := getRandNum(testCase.givenVal)
|
randNum, err := random.GetNum(testCase.givenVal)
|
||||||
if testCase.shouldFail {
|
if testCase.shouldFail {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Random number generation succeeded but was expected to fail. Given: %v, returned: %v",
|
t.Errorf("Random number generation succeeded but was expected to fail. Given: %v, returned: %v",
|
||||||
|
@ -60,20 +64,20 @@ func TestGenLength(t *testing.T) {
|
||||||
minLength int
|
minLength int
|
||||||
maxLength int
|
maxLength int
|
||||||
}{
|
}{
|
||||||
{"pwLength defaults", DefaultMinLenght, DefaultMaxLenght},
|
{"pwLength defaults", config.DefaultMinLength, config.DefaultMaxLength},
|
||||||
{"pwLength 0 to 1", 0, 1},
|
{"pwLength 0 to 1", 0, 1},
|
||||||
{"pwLength 1 to 10", 0, 10},
|
{"pwLength 1 to 10", 0, 10},
|
||||||
{"pwLength 10 to 100", 10, 100},
|
{"pwLength 10 to 100", 10, 100},
|
||||||
}
|
}
|
||||||
|
|
||||||
charRange := getCharRange(&config)
|
charRange := chars.GetRange(&cfgObj)
|
||||||
for _, testCase := range testTable {
|
for _, testCase := range testTable {
|
||||||
t.Run(testCase.testName, func(t *testing.T) {
|
t.Run(testCase.testName, func(t *testing.T) {
|
||||||
config.minPassLen = testCase.minLength
|
cfgObj.MinPassLen = testCase.minLength
|
||||||
config.maxPassLen = testCase.maxLength
|
cfgObj.MaxPassLen = testCase.maxLength
|
||||||
pwLength := getPwLengthFromParams(&config)
|
pwLength := config.GetPwLengthFromParams(&cfgObj)
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
pwString, err := getRandChar(&charRange, pwLength)
|
pwString, err := random.GetChar(&charRange, pwLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("getRandChar returned an error: %q", err)
|
t.Errorf("getRandChar returned an error: %q", err)
|
||||||
}
|
}
|
||||||
|
@ -95,7 +99,7 @@ func TestGenLength(t *testing.T) {
|
||||||
func TestGetRandChar(t *testing.T) {
|
func TestGetRandChar(t *testing.T) {
|
||||||
t.Run("return_value_is_A_B_or_C", func(t *testing.T) {
|
t.Run("return_value_is_A_B_or_C", func(t *testing.T) {
|
||||||
charRange := "ABC"
|
charRange := "ABC"
|
||||||
randChar, err := getRandChar(&charRange, 1)
|
randChar, err := random.GetChar(&charRange, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Random character generation failed => %v", err.Error())
|
t.Fatalf("Random character generation failed => %v", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -106,7 +110,7 @@ func TestGetRandChar(t *testing.T) {
|
||||||
|
|
||||||
t.Run("return_value_has_specific_length", func(t *testing.T) {
|
t.Run("return_value_has_specific_length", func(t *testing.T) {
|
||||||
charRange := "ABC"
|
charRange := "ABC"
|
||||||
randChar, err := getRandChar(&charRange, 1000)
|
randChar, err := random.GetChar(&charRange, 1000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Random character generation failed => %v", err.Error())
|
t.Fatalf("Random character generation failed => %v", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -118,7 +122,7 @@ func TestGetRandChar(t *testing.T) {
|
||||||
|
|
||||||
t.Run("fail", func(t *testing.T) {
|
t.Run("fail", func(t *testing.T) {
|
||||||
charRange := "ABC"
|
charRange := "ABC"
|
||||||
randChar, err := getRandChar(&charRange, -2000)
|
randChar, err := random.GetChar(&charRange, -2000)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("Generated random characters expected to fail, but returned a value => %v",
|
t.Fatalf("Generated random characters expected to fail, but returned a value => %v",
|
||||||
randChar)
|
randChar)
|
||||||
|
@ -126,7 +130,7 @@ func TestGetRandChar(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test getCharRange() with different config settings
|
// Test getCharRange() with different cfgObj settings
|
||||||
func TestGetCharRange(t *testing.T) {
|
func TestGetCharRange(t *testing.T) {
|
||||||
lowerCaseBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
|
lowerCaseBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
|
||||||
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
|
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
|
||||||
|
@ -162,12 +166,12 @@ func TestGetCharRange(t *testing.T) {
|
||||||
|
|
||||||
for _, testCase := range testTable {
|
for _, testCase := range testTable {
|
||||||
t.Run(testCase.testName, func(t *testing.T) {
|
t.Run(testCase.testName, func(t *testing.T) {
|
||||||
config.useLowerCase = testCase.useLowerCase
|
cfgObj.UseLowerCase = testCase.useLowerCase
|
||||||
config.useUpperCase = testCase.useUpperCase
|
cfgObj.UseUpperCase = testCase.useUpperCase
|
||||||
config.useNumber = testCase.useNumber
|
cfgObj.UseNumber = testCase.useNumber
|
||||||
config.useSpecial = testCase.useSpecial
|
cfgObj.UseSpecial = testCase.useSpecial
|
||||||
config.humanReadable = testCase.humanReadable
|
cfgObj.HumanReadable = testCase.humanReadable
|
||||||
charRange := getCharRange(&config)
|
charRange := chars.GetRange(&cfgObj)
|
||||||
for _, curChar := range charRange {
|
for _, curChar := range charRange {
|
||||||
searchAllowedBytes := containsByte(testCase.allowedBytes, int(curChar), t)
|
searchAllowedBytes := containsByte(testCase.allowedBytes, int(curChar), t)
|
||||||
if !searchAllowedBytes {
|
if !searchAllowedBytes {
|
||||||
|
@ -194,7 +198,7 @@ func TestConvert(t *testing.T) {
|
||||||
|
|
||||||
for _, testCase := range testTable {
|
for _, testCase := range testTable {
|
||||||
t.Run(testCase.testName, func(t *testing.T) {
|
t.Run(testCase.testName, func(t *testing.T) {
|
||||||
charToString, err := convertCharToName(testCase.givenVal)
|
charToString, err := spelling.ConvertCharToName(testCase.givenVal)
|
||||||
if testCase.shouldFail {
|
if testCase.shouldFail {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Character to string conversion succeeded but was expected to fail. Given: %v, returned: %v",
|
t.Errorf("Character to string conversion succeeded but was expected to fail. Given: %v, returned: %v",
|
||||||
|
@ -213,14 +217,14 @@ func TestConvert(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("all_chars_must_return_a_conversion_string", func(t *testing.T) {
|
t.Run("all_chars_must_return_a_conversion_string", func(t *testing.T) {
|
||||||
config.useUpperCase = true
|
cfgObj.UseUpperCase = true
|
||||||
config.useLowerCase = true
|
cfgObj.UseLowerCase = true
|
||||||
config.useNumber = true
|
cfgObj.UseNumber = true
|
||||||
config.useSpecial = true
|
cfgObj.UseSpecial = true
|
||||||
config.humanReadable = false
|
cfgObj.HumanReadable = false
|
||||||
charRange := getCharRange(&config)
|
charRange := chars.GetRange(&cfgObj)
|
||||||
for _, curChar := range charRange {
|
for _, curChar := range charRange {
|
||||||
_, err := convertCharToName(byte(curChar))
|
_, err := spelling.ConvertCharToName(byte(curChar))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Character to string conversion failed: %v", err.Error())
|
t.Fatalf("Character to string conversion failed: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -228,7 +232,7 @@ func TestConvert(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("spell_Ab!_to_strings", func(t *testing.T) {
|
t.Run("spell_Ab!_to_strings", func(t *testing.T) {
|
||||||
pwString := "Ab!"
|
pwString := "Ab!"
|
||||||
spelledString, err := spellPasswordString(pwString)
|
spelledString, err := spelling.String(pwString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("password spelling failed: %v", err.Error())
|
t.Fatalf("password spelling failed: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -243,7 +247,7 @@ func TestConvert(t *testing.T) {
|
||||||
// Benchmark: Random number generation
|
// Benchmark: Random number generation
|
||||||
func BenchmarkGetRandNum(b *testing.B) {
|
func BenchmarkGetRandNum(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = getRandNum(100000)
|
_, _ = random.GetNum(100000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,23 +255,23 @@ func BenchmarkGetRandNum(b *testing.B) {
|
||||||
func BenchmarkGetRandChar(b *testing.B) {
|
func BenchmarkGetRandChar(b *testing.B) {
|
||||||
charRange := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\"#/!\\$%&+-*.,?=()[]{}:;~^|"
|
charRange := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\"#/!\\$%&+-*.,?=()[]{}:;~^|"
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = getRandChar(&charRange, 20)
|
_, _ = random.GetChar(&charRange, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark: Random char generation
|
// Benchmark: Random char generation
|
||||||
func BenchmarkConvertChar(b *testing.B) {
|
func BenchmarkConvertChar(b *testing.B) {
|
||||||
|
|
||||||
config.useUpperCase = true
|
cfgObj.UseUpperCase = true
|
||||||
config.useLowerCase = true
|
cfgObj.UseLowerCase = true
|
||||||
config.useNumber = true
|
cfgObj.UseNumber = true
|
||||||
config.useSpecial = true
|
cfgObj.UseSpecial = true
|
||||||
config.humanReadable = false
|
cfgObj.HumanReadable = false
|
||||||
charRange := getCharRange(&config)
|
charRange := chars.GetRange(&cfgObj)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
charToConv, _ := getRandChar(&charRange, 1)
|
charToConv, _ := random.GetChar(&charRange, 1)
|
||||||
charBytes := []byte(charToConv)
|
charBytes := []byte(charToConv)
|
||||||
_, _ = convertCharToName(charBytes[0])
|
_, _ = spelling.ConvertCharToName(charBytes[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
164
config.go
164
config.go
|
@ -1,164 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parse the CLI flags
|
|
||||||
func parseFlags() Config {
|
|
||||||
var switchConf Config
|
|
||||||
defaultSwitches := Config{
|
|
||||||
useLowerCase: true,
|
|
||||||
useUpperCase: true,
|
|
||||||
useNumber: true,
|
|
||||||
useSpecial: false,
|
|
||||||
useComplex: false,
|
|
||||||
humanReadable: false,
|
|
||||||
}
|
|
||||||
config := Config{
|
|
||||||
useLowerCase: defaultSwitches.useLowerCase,
|
|
||||||
useUpperCase: defaultSwitches.useUpperCase,
|
|
||||||
useNumber: defaultSwitches.useNumber,
|
|
||||||
useSpecial: defaultSwitches.useSpecial,
|
|
||||||
useComplex: defaultSwitches.useComplex,
|
|
||||||
humanReadable: defaultSwitches.humanReadable,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read and set all flags
|
|
||||||
flag.BoolVar(&switchConf.useLowerCase, "L", false, "Use lower case characters in passwords")
|
|
||||||
flag.BoolVar(&switchConf.useUpperCase, "U", false, "Use upper case characters in passwords")
|
|
||||||
flag.BoolVar(&switchConf.useNumber, "N", false, "Use numerich characters in passwords")
|
|
||||||
flag.BoolVar(&switchConf.useSpecial, "S", false, "Use special characters in passwords")
|
|
||||||
flag.BoolVar(&switchConf.useComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)")
|
|
||||||
flag.BoolVar(&switchConf.humanReadable, "H", false, "Generate human-readable passwords")
|
|
||||||
flag.BoolVar(&config.spellPassword, "l", false, "Spell generated password")
|
|
||||||
flag.BoolVar(&config.checkHibp, "p", false, "Check the HIBP database if the generated password was leaked before")
|
|
||||||
flag.BoolVar(&config.showVersion, "v", false, "Show version")
|
|
||||||
flag.IntVar(&config.minPassLen, "m", DefaultMinLenght, "Minimum password length")
|
|
||||||
flag.IntVar(&config.maxPassLen, "x", DefaultMaxLenght, "Maxiumum password length")
|
|
||||||
flag.IntVar(&config.numOfPass, "n", 6, "Number of passwords to generate")
|
|
||||||
flag.StringVar(&config.excludeChars, "E", "", "Exclude list of characters from generated password")
|
|
||||||
flag.StringVar(&config.newStyleModes, "M", "",
|
|
||||||
"New style password parameters (higher priority than single parameters)")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// Invert-switch the defaults
|
|
||||||
if switchConf.useLowerCase {
|
|
||||||
config.useLowerCase = !defaultSwitches.useLowerCase
|
|
||||||
}
|
|
||||||
if switchConf.useUpperCase {
|
|
||||||
config.useUpperCase = !defaultSwitches.useUpperCase
|
|
||||||
}
|
|
||||||
if switchConf.useNumber {
|
|
||||||
config.useNumber = !defaultSwitches.useNumber
|
|
||||||
}
|
|
||||||
if switchConf.useSpecial {
|
|
||||||
config.useSpecial = !defaultSwitches.useSpecial
|
|
||||||
}
|
|
||||||
if switchConf.useComplex {
|
|
||||||
config.useComplex = !defaultSwitches.useComplex
|
|
||||||
}
|
|
||||||
if switchConf.humanReadable {
|
|
||||||
config.humanReadable = !defaultSwitches.humanReadable
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse additional parameters and new-style switches
|
|
||||||
parseParams(&config)
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the parameters and set the according config flags
|
|
||||||
func parseParams(config *Config) {
|
|
||||||
parseNewStyleParams(config)
|
|
||||||
|
|
||||||
// Complex overrides everything
|
|
||||||
if config.useComplex {
|
|
||||||
config.useUpperCase = true
|
|
||||||
config.useLowerCase = true
|
|
||||||
config.useSpecial = true
|
|
||||||
config.useNumber = true
|
|
||||||
config.humanReadable = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.useUpperCase == false &&
|
|
||||||
config.useLowerCase == false &&
|
|
||||||
config.useNumber == false &&
|
|
||||||
config.useSpecial == false {
|
|
||||||
log.Fatalf("No password mode set. Cannot generate password from empty character set.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set output mode
|
|
||||||
if config.spellPassword {
|
|
||||||
config.outputMode = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the password length from the given cli flags
|
|
||||||
func getPwLengthFromParams(config *Config) int {
|
|
||||||
if config.minPassLen > config.maxPassLen {
|
|
||||||
config.maxPassLen = config.minPassLen
|
|
||||||
}
|
|
||||||
lenDiff := config.maxPassLen - config.minPassLen + 1
|
|
||||||
randAdd, err := getRandNum(lenDiff)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to generated password length: %v", err)
|
|
||||||
}
|
|
||||||
retVal := config.minPassLen + randAdd
|
|
||||||
if retVal <= 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the new style parameters
|
|
||||||
func parseNewStyleParams(config *Config) {
|
|
||||||
if config.newStyleModes == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, curParam := range config.newStyleModes {
|
|
||||||
switch curParam {
|
|
||||||
case 'S':
|
|
||||||
config.useSpecial = true
|
|
||||||
break
|
|
||||||
case 's':
|
|
||||||
config.useSpecial = false
|
|
||||||
break
|
|
||||||
case 'N':
|
|
||||||
config.useNumber = true
|
|
||||||
break
|
|
||||||
case 'n':
|
|
||||||
config.useNumber = false
|
|
||||||
break
|
|
||||||
case 'L':
|
|
||||||
config.useLowerCase = true
|
|
||||||
break
|
|
||||||
case 'l':
|
|
||||||
config.useLowerCase = false
|
|
||||||
break
|
|
||||||
case 'U':
|
|
||||||
config.useUpperCase = true
|
|
||||||
break
|
|
||||||
case 'u':
|
|
||||||
config.useUpperCase = false
|
|
||||||
break
|
|
||||||
case 'H':
|
|
||||||
config.humanReadable = true
|
|
||||||
break
|
|
||||||
case 'h':
|
|
||||||
config.humanReadable = false
|
|
||||||
break
|
|
||||||
case 'C':
|
|
||||||
config.useComplex = true
|
|
||||||
break
|
|
||||||
case 'c':
|
|
||||||
config.useComplex = false
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
log.Fatalf("Unknown password style parameter: %q\n", string(curParam))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
181
config/config.go
Normal file
181
config/config.go
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"github.com/wneessen/apg-go/random"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a struct that holds the different config parameters for the apg-go
|
||||||
|
// application
|
||||||
|
type Config struct {
|
||||||
|
MinPassLen int
|
||||||
|
MaxPassLen int
|
||||||
|
NumOfPass int
|
||||||
|
UseComplex bool
|
||||||
|
UseLowerCase bool
|
||||||
|
UseUpperCase bool
|
||||||
|
UseNumber bool
|
||||||
|
UseSpecial bool
|
||||||
|
HumanReadable bool
|
||||||
|
CheckHibp bool
|
||||||
|
ExcludeChars string
|
||||||
|
NewStyleModes string
|
||||||
|
SpellPassword bool
|
||||||
|
ShowHelp bool
|
||||||
|
ShowVersion bool
|
||||||
|
OutputMode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMinLength reflects the default minimum length of a generated password
|
||||||
|
const DefaultMinLength int = 12
|
||||||
|
|
||||||
|
// DefaultMaxLength reflects the default maximum length of a generated password
|
||||||
|
const DefaultMaxLength int = 20
|
||||||
|
|
||||||
|
// New parses the CLI flags and returns a new config object
|
||||||
|
func New() Config {
|
||||||
|
var switchConf Config
|
||||||
|
defaultSwitches := Config{
|
||||||
|
UseLowerCase: true,
|
||||||
|
UseUpperCase: true,
|
||||||
|
UseNumber: true,
|
||||||
|
UseSpecial: false,
|
||||||
|
UseComplex: false,
|
||||||
|
HumanReadable: false,
|
||||||
|
}
|
||||||
|
config := Config{
|
||||||
|
UseLowerCase: defaultSwitches.UseLowerCase,
|
||||||
|
UseUpperCase: defaultSwitches.UseUpperCase,
|
||||||
|
UseNumber: defaultSwitches.UseNumber,
|
||||||
|
UseSpecial: defaultSwitches.UseSpecial,
|
||||||
|
UseComplex: defaultSwitches.UseComplex,
|
||||||
|
HumanReadable: defaultSwitches.HumanReadable,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and set all flags
|
||||||
|
flag.BoolVar(&switchConf.UseLowerCase, "L", false, "Use lower case characters in passwords")
|
||||||
|
flag.BoolVar(&switchConf.UseUpperCase, "U", false, "Use upper case characters in passwords")
|
||||||
|
flag.BoolVar(&switchConf.UseNumber, "N", false, "Use numerich characters in passwords")
|
||||||
|
flag.BoolVar(&switchConf.UseSpecial, "S", false, "Use special characters in passwords")
|
||||||
|
flag.BoolVar(&switchConf.UseComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)")
|
||||||
|
flag.BoolVar(&switchConf.HumanReadable, "H", false, "Generate human-readable passwords")
|
||||||
|
flag.BoolVar(&config.SpellPassword, "l", false, "Spell generated password")
|
||||||
|
flag.BoolVar(&config.CheckHibp, "p", false, "Check the HIBP database if the generated password was leaked before")
|
||||||
|
flag.BoolVar(&config.ShowVersion, "v", false, "Show version")
|
||||||
|
flag.IntVar(&config.MinPassLen, "m", DefaultMinLength, "Minimum password length")
|
||||||
|
flag.IntVar(&config.MaxPassLen, "x", DefaultMaxLength, "Maxiumum password length")
|
||||||
|
flag.IntVar(&config.NumOfPass, "n", 6, "Number of passwords to generate")
|
||||||
|
flag.StringVar(&config.ExcludeChars, "E", "", "Exclude list of characters from generated password")
|
||||||
|
flag.StringVar(&config.NewStyleModes, "M", "",
|
||||||
|
"New style password parameters (higher priority than single parameters)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Invert-switch the defaults
|
||||||
|
if switchConf.UseLowerCase {
|
||||||
|
config.UseLowerCase = !defaultSwitches.UseLowerCase
|
||||||
|
}
|
||||||
|
if switchConf.UseUpperCase {
|
||||||
|
config.UseUpperCase = !defaultSwitches.UseUpperCase
|
||||||
|
}
|
||||||
|
if switchConf.UseNumber {
|
||||||
|
config.UseNumber = !defaultSwitches.UseNumber
|
||||||
|
}
|
||||||
|
if switchConf.UseSpecial {
|
||||||
|
config.UseSpecial = !defaultSwitches.UseSpecial
|
||||||
|
}
|
||||||
|
if switchConf.UseComplex {
|
||||||
|
config.UseComplex = !defaultSwitches.UseComplex
|
||||||
|
}
|
||||||
|
if switchConf.HumanReadable {
|
||||||
|
config.HumanReadable = !defaultSwitches.HumanReadable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse additional parameters and new-style switches
|
||||||
|
parseParams(&config)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the parameters and set the according config flags
|
||||||
|
func parseParams(config *Config) {
|
||||||
|
parseNewStyleParams(config)
|
||||||
|
|
||||||
|
// Complex overrides everything
|
||||||
|
if config.UseComplex {
|
||||||
|
config.UseUpperCase = true
|
||||||
|
config.UseLowerCase = true
|
||||||
|
config.UseSpecial = true
|
||||||
|
config.UseNumber = true
|
||||||
|
config.HumanReadable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.UseUpperCase &&
|
||||||
|
!config.UseLowerCase &&
|
||||||
|
!config.UseNumber &&
|
||||||
|
!config.UseSpecial {
|
||||||
|
log.Fatalf("No password mode set. Cannot generate password from empty character set.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set output mode
|
||||||
|
if config.SpellPassword {
|
||||||
|
config.OutputMode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the new style parameters
|
||||||
|
func parseNewStyleParams(config *Config) {
|
||||||
|
if config.NewStyleModes == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, curParam := range config.NewStyleModes {
|
||||||
|
switch curParam {
|
||||||
|
case 'S':
|
||||||
|
config.UseSpecial = true
|
||||||
|
case 's':
|
||||||
|
config.UseSpecial = false
|
||||||
|
case 'N':
|
||||||
|
config.UseNumber = true
|
||||||
|
case 'n':
|
||||||
|
config.UseNumber = false
|
||||||
|
case 'L':
|
||||||
|
config.UseLowerCase = true
|
||||||
|
case 'l':
|
||||||
|
config.UseLowerCase = false
|
||||||
|
case 'U':
|
||||||
|
config.UseUpperCase = true
|
||||||
|
case 'u':
|
||||||
|
config.UseUpperCase = false
|
||||||
|
case 'H':
|
||||||
|
config.HumanReadable = true
|
||||||
|
case 'h':
|
||||||
|
config.HumanReadable = false
|
||||||
|
case 'C':
|
||||||
|
config.UseComplex = true
|
||||||
|
case 'c':
|
||||||
|
config.UseComplex = false
|
||||||
|
default:
|
||||||
|
log.Fatalf("Unknown password style parameter: %q\n", string(curParam))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPwLengthFromParams extracts the password length from the given cli flags and stores
|
||||||
|
// in the provided config object
|
||||||
|
func GetPwLengthFromParams(config *Config) int {
|
||||||
|
if config.MinPassLen > config.MaxPassLen {
|
||||||
|
config.MaxPassLen = config.MinPassLen
|
||||||
|
}
|
||||||
|
lenDiff := config.MaxPassLen - config.MinPassLen + 1
|
||||||
|
randAdd, err := random.GetNum(lenDiff)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to generated password length: %v", err)
|
||||||
|
}
|
||||||
|
retVal := config.MinPassLen + randAdd
|
||||||
|
if retVal <= 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
}
|
2
go.mod
2
go.mod
|
@ -1,3 +1,5 @@
|
||||||
module github.com/wneessen/apg-go
|
module github.com/wneessen/apg-go
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
|
require github.com/wneessen/go-hibp v1.0.0
|
||||||
|
|
2
go.sum
Normal file
2
go.sum
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/wneessen/go-hibp v1.0.0 h1:XmjymVwdhH06TVzU/ahlYzwAhX3OgxoxvyJ3HPa8gks=
|
||||||
|
github.com/wneessen/go-hibp v1.0.0/go.mod h1:Ldg6DQg4fMCveVKgL+RL9Jy+9TsljjAP704Ix8X3jOw=
|
44
hibp.go
44
hibp.go
|
@ -1,44 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/sha1"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkHibp(p string) (bool, error) {
|
|
||||||
shaSum := fmt.Sprintf("%x", sha1.Sum([]byte(p)))
|
|
||||||
firstPart := shaSum[0:5]
|
|
||||||
secondPart := shaSum[5:]
|
|
||||||
isPwned := false
|
|
||||||
|
|
||||||
httpClient := &http.Client{Timeout: time.Second * 2}
|
|
||||||
httpRes, err := httpClient.Get("https://api.pwnedpasswords.com/range/" + firstPart)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err := httpRes.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error while closing HTTP response body: %v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
scanObj := bufio.NewScanner(httpRes.Body)
|
|
||||||
for scanObj.Scan() {
|
|
||||||
scanLine := strings.SplitN(scanObj.Text(), ":", 2)
|
|
||||||
if strings.ToLower(scanLine[0]) == secondPart {
|
|
||||||
isPwned = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanObj.Err(); err != nil {
|
|
||||||
return isPwned, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return isPwned, nil
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package random
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
@ -6,9 +6,9 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generate random characters based on given character range
|
// GetChar generates random characters based on given character range
|
||||||
// and password length
|
// and password length
|
||||||
func getRandChar(charRange *string, pwLength int) (string, error) {
|
func GetChar(charRange *string, pwLength int) (string, error) {
|
||||||
if pwLength <= 0 {
|
if pwLength <= 0 {
|
||||||
err := fmt.Errorf("provided pwLength value is <= 0: %v", pwLength)
|
err := fmt.Errorf("provided pwLength value is <= 0: %v", pwLength)
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -17,7 +17,7 @@ func getRandChar(charRange *string, pwLength int) (string, error) {
|
||||||
charSlice := []byte(*charRange)
|
charSlice := []byte(*charRange)
|
||||||
returnString := make([]byte, pwLength)
|
returnString := make([]byte, pwLength)
|
||||||
for i := 0; i < pwLength; i++ {
|
for i := 0; i < pwLength; i++ {
|
||||||
randNum, err := getRandNum(availCharsLength)
|
randNum, err := GetNum(availCharsLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@ func getRandChar(charRange *string, pwLength int) (string, error) {
|
||||||
return string(returnString), nil
|
return string(returnString), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a random number with given maximum value
|
// GetNum generates a random number with given maximum value
|
||||||
func getRandNum(maxNum int) (int, error) {
|
func GetNum(maxNum int) (int, error) {
|
||||||
if maxNum <= 0 {
|
if maxNum <= 0 {
|
||||||
err := fmt.Errorf("provided maxNum is <= 0: %v", maxNum)
|
err := fmt.Errorf("provided maxNum is <= 0: %v", maxNum)
|
||||||
return 0, err
|
return 0, err
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package spelling
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -80,10 +80,11 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func spellPasswordString(pwString string) (string, error) {
|
// String returns an english spelled version of the given string
|
||||||
|
func String(pwString string) (string, error) {
|
||||||
var returnString []string
|
var returnString []string
|
||||||
for _, curChar := range pwString {
|
for _, curChar := range pwString {
|
||||||
curSpellString, err := convertCharToName(byte(curChar))
|
curSpellString, err := ConvertCharToName(byte(curChar))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -92,7 +93,9 @@ func spellPasswordString(pwString string) (string, error) {
|
||||||
return strings.Join(returnString, "/"), nil
|
return strings.Join(returnString, "/"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertCharToName(charByte byte) (string, error) {
|
// ConvertCharToName converts a given ascii byte into the corresponding english spelled
|
||||||
|
// name
|
||||||
|
func ConvertCharToName(charByte byte) (string, error) {
|
||||||
var returnString string
|
var returnString string
|
||||||
if charByte > 64 && charByte < 91 {
|
if charByte > 64 && charByte < 91 {
|
||||||
returnString = alphabetNames[charByte]
|
returnString = alphabetNames[charByte]
|
Loading…
Reference in a new issue