Skip to content

Commit bc01cb0

Browse files
committed
Validation of GH profile #20
1 parent c98d29b commit bc01cb0

File tree

6 files changed

+528
-105
lines changed

6 files changed

+528
-105
lines changed

samples/validation_gist.json

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"url": "https://api.github.com/gists/fb8fc0f87ee78231f064131022c8154a",
3+
"forks_url": "https://api.github.com/gists/fb8fc0f87ee78231f064131022c8154a/forks",
4+
"commits_url": "https://api.github.com/gists/fb8fc0f87ee78231f064131022c8154a/commits",
5+
"id": "fb8fc0f87ee78231f064131022c8154a",
6+
"node_id": "MDQ6R2lzdGZiOGZjMGY4N2VlNzgyMzFmMDY0MTMxMDIyYzgxNTRh",
7+
"git_pull_url": "https://gist.github.com/fb8fc0f87ee78231f064131022c8154a.git",
8+
"git_push_url": "https://gist.github.com/fb8fc0f87ee78231f064131022c8154a.git",
9+
"html_url": "https://gist.github.com/fb8fc0f87ee78231f064131022c8154a",
10+
"files": {
11+
"stm.txt": {
12+
"filename": "stm.txt",
13+
"type": "text/plain",
14+
"language": "Text",
15+
"raw_url": "https://gist.githubusercontent.com/rimutaka/fb8fc0f87ee78231f064131022c8154a/raw/1e99cbb2ae82c4ebfb3df7195a150f81142b894a/stm.txt",
16+
"size": 17,
17+
"truncated": false,
18+
"content": "MDQ6R2lzdGZiOGZjMGY4N2VlNzgyMzFmMDY0MTMxMDIyYzgxNTRh"
19+
}
20+
},
21+
"public": false,
22+
"created_at": "2021-08-14T04:20:07Z",
23+
"updated_at": "2021-08-14T04:20:07Z",
24+
"description": "",
25+
"comments": 0,
26+
"user": null,
27+
"comments_url": "https://api.github.com/gists/fb8fc0f87ee78231f064131022c8154a/comments",
28+
"owner": {
29+
"login": "rimutaka",
30+
"id": 5926028,
31+
"node_id": "MDQ6VXNlcjU5MjYwMjg=",
32+
"avatar_url": "https://avatars.githubusercontent.com/u/5926028?v=4",
33+
"gravatar_id": "",
34+
"url": "https://api.github.com/users/rimutaka",
35+
"html_url": "https://github.com/rimutaka",
36+
"followers_url": "https://api.github.com/users/rimutaka/followers",
37+
"following_url": "https://api.github.com/users/rimutaka/following{/other_user}",
38+
"gists_url": "https://api.github.com/users/rimutaka/gists{/gist_id}",
39+
"starred_url": "https://api.github.com/users/rimutaka/starred{/owner}{/repo}",
40+
"subscriptions_url": "https://api.github.com/users/rimutaka/subscriptions",
41+
"organizations_url": "https://api.github.com/users/rimutaka/orgs",
42+
"repos_url": "https://api.github.com/users/rimutaka/repos",
43+
"events_url": "https://api.github.com/users/rimutaka/events{/privacy}",
44+
"received_events_url": "https://api.github.com/users/rimutaka/received_events",
45+
"type": "User",
46+
"site_admin": false
47+
},
48+
"forks": [],
49+
"history": [
50+
{
51+
"user": {
52+
"login": "rimutaka",
53+
"id": 5926028,
54+
"node_id": "MDQ6VXNlcjU5MjYwMjg=",
55+
"avatar_url": "https://avatars.githubusercontent.com/u/5926028?v=4",
56+
"gravatar_id": "",
57+
"url": "https://api.github.com/users/rimutaka",
58+
"html_url": "https://github.com/rimutaka",
59+
"followers_url": "https://api.github.com/users/rimutaka/followers",
60+
"following_url": "https://api.github.com/users/rimutaka/following{/other_user}",
61+
"gists_url": "https://api.github.com/users/rimutaka/gists{/gist_id}",
62+
"starred_url": "https://api.github.com/users/rimutaka/starred{/owner}{/repo}",
63+
"subscriptions_url": "https://api.github.com/users/rimutaka/subscriptions",
64+
"organizations_url": "https://api.github.com/users/rimutaka/orgs",
65+
"repos_url": "https://api.github.com/users/rimutaka/repos",
66+
"events_url": "https://api.github.com/users/rimutaka/events{/privacy}",
67+
"received_events_url": "https://api.github.com/users/rimutaka/received_events",
68+
"type": "User",
69+
"site_admin": false
70+
},
71+
"version": "0d2c74fcd6167f9a2de514ed75d48bba2bd8361a",
72+
"committed_at": "2021-08-14T04:20:07Z",
73+
"change_status": {
74+
"total": 1,
75+
"additions": 1,
76+
"deletions": 0
77+
},
78+
"url": "https://api.github.com/gists/fb8fc0f87ee78231f064131022c8154a/0d2c74fcd6167f9a2de514ed75d48bba2bd8361a"
79+
}
80+
],
81+
"truncated": false
82+
}

stackmuncher/src/app_args.rs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
use crate::help;
22
use pico_args;
3+
use regex::Regex;
4+
use std::env::consts::EXE_SUFFIX;
35
use std::str::FromStr;
46
use std::{path::PathBuf, process::exit};
57

8+
pub(crate) const GIST_ID_REGEX: &str = "[a-f0-9]{32}";
9+
610
/// List of valid app commands
711
#[derive(PartialEq)]
812
pub(crate) enum AppArgCommands {
@@ -16,6 +20,8 @@ pub(crate) enum AppArgCommands {
1620
MakeAnon,
1721
/// Completely delete the member profile from the directory
1822
DeleteProfile,
23+
/// Configure Github validation page
24+
GitGHubConfig,
1925
}
2026

2127
/// A container for user-provided CLI commands and params. The names of the members correspond
@@ -25,8 +31,9 @@ pub(crate) struct AppArgs {
2531
pub dryrun: bool,
2632
pub primary_email: Option<String>,
2733
pub emails: Option<Vec<String>>,
28-
pub public_name: Option<String>,
29-
pub public_contact: Option<String>,
34+
/// A 32-byte long hex string of the Gist ID with the validation string for the user's GH account
35+
/// E.g. `fb8fc0f87ee78231f064131022c8154a`
36+
pub gh_validation_id: Option<String>,
3037
pub project: Option<PathBuf>,
3138
pub rules: Option<PathBuf>,
3239
pub reports: Option<PathBuf>,
@@ -45,6 +52,7 @@ impl FromStr for AppArgCommands {
4552
"viewconfig" | "view-config" | "view_config" | "config" => Self::ViewConfig,
4653
"makeanon" | "make-anon" | "make_anon" => Self::MakeAnon,
4754
"deleteprofile" | "delete-profile" | "delete_profile" | "delete" => Self::DeleteProfile,
55+
"github" => Self::GitGHubConfig,
4856
_ => {
4957
eprintln!("STACKMUNCHER CONFIG ERROR: invalid command `{}`", command);
5058
help::emit_usage_msg();
@@ -65,8 +73,7 @@ impl AppArgs {
6573
dryrun: false,
6674
primary_email: None,
6775
emails: None,
68-
public_name: None,
69-
public_contact: None,
76+
gh_validation_id: None,
7077
project: None,
7178
rules: None,
7279
reports: None,
@@ -124,15 +131,35 @@ impl AppArgs {
124131
app_args.emails = Some(emails);
125132
};
126133

127-
// --public_name
128-
if let Some(public_name) = find_arg_value(&mut pargs, vec!["--public_name", "--public-name", "--publicname"]) {
129-
app_args.public_name = Some(public_name);
130-
};
131-
// --public_contact
132-
if let Some(public_contact) =
133-
find_arg_value(&mut pargs, vec!["--public_contact", "--public-contact", "--public_contact"])
134-
{
135-
app_args.public_contact = Some(public_contact);
134+
// --gist
135+
if let Some(gist_url) = find_arg_value(&mut pargs, vec!["--gist"]) {
136+
// extract the gist id from the input, which can be the full URL, just the ID or the raw URL which is even longer
137+
// e.g. fb8fc0f87ee78231f064131022c8154a
138+
// or https://gist.github.com/rimutaka/fb8fc0f87ee78231f064131022c8154a
139+
// or https://gist.githubusercontent.com/rimutaka/fb8fc0f87ee78231f064131022c8154a
140+
// or https://gist.githubusercontent.com/rimutaka/fb8fc0f87ee78231f064131022c8154a/raw/1e99cbb2ae82c4ebfb3df7195a150f81142b894a/stm.txt
141+
142+
if gist_url.is_empty() {
143+
// the user requested removal of GH login
144+
app_args.gh_validation_id = Some(String::new());
145+
} else if let Some(matches) = Regex::new(GIST_ID_REGEX)
146+
.expect("Invalid gist_id_regex. It's a bug.")
147+
.find(&gist_url)
148+
{
149+
// some value with a likely-looking gist id was provided
150+
app_args.gh_validation_id = Some(matches.as_str().to_string());
151+
} else {
152+
// some other value was provided
153+
eprintln!("STACKMUNCHER CONFIG ERROR: param `--gist` has an invalid value.",);
154+
eprintln!();
155+
eprintln!(" It accepts either the Gist URL or the Gist ID found in the Gist URL:",);
156+
eprintln!(" * https://gist.github.com/rimutaka/fb8fc0f87ee78231f064131022c8154a");
157+
eprintln!(" * fb8fc0f87ee78231f064131022c8154a");
158+
eprintln!();
159+
eprintln!(" To unlink from GitHub and remove private projects from your public profile use `stackmuncher{} --gist \"\"`", EXE_SUFFIX);
160+
help::emit_usage_msg();
161+
exit(1);
162+
}
136163
};
137164

138165
// project folder
@@ -258,7 +285,7 @@ fn find_arg_value(pargs: &mut pico_args::Arguments, arg_names: Vec<&'static str>
258285
Ok(v) => v,
259286
Err(_) => {
260287
eprintln!(
261-
"STACKMUNCHER CONFIG ERROR: invalid or missing value for `{}`. Add \"\" to reset the value.",
288+
"STACKMUNCHER CONFIG ERROR: invalid or missing value for `{}`. Add \"\" to reset this setting.",
262289
arg_name
263290
);
264291
help::emit_usage_msg();

stackmuncher/src/cmd_munch.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,6 @@ pub(crate) async fn run(config: AppConfig) -> Result<(), ()> {
155155
combined_report.recompute_tech_section();
156156

157157
// add any personal details supplied via CLI or taken from the environment
158-
combined_report.public_name = config.public_name.clone();
159-
combined_report.public_contact = config.public_contact.clone();
160158
combined_report.primary_email = config.primary_email.clone();
161159

162160
// check if there is a already a cached contributor report

stackmuncher/src/config.rs

Lines changed: 27 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,12 @@ pub(crate) struct AppConfig {
2626
pub command: AppArgCommands,
2727
pub dryrun: bool,
2828
pub primary_email: Option<String>,
29-
pub public_name: Option<String>,
30-
pub public_contact: Option<String>,
29+
/// A 32-byte long hex string of the Gist ID with the validation string for the user GH account
30+
/// E.g. `fb8fc0f87ee78231f064131022c8154a`
31+
pub gh_validation_id: Option<String>,
32+
/// The URL is reconstructed from a Gist ID after validation.
33+
/// It may seize to exist or change the contents, but the URL itself is guaranteed to be valid
34+
pub gh_validation_url: Option<String>,
3135
/// Core config from stackmuncher_lib
3236
pub lib_config: Config,
3337
/// Extracted from the key file stored next to the config file
@@ -40,8 +44,9 @@ pub(crate) struct AppConfig {
4044
#[derive(Serialize, Deserialize, PartialEq)]
4145
struct AppConfigCache {
4246
pub primary_email: Option<String>,
43-
pub public_name: Option<String>,
44-
pub public_contact: Option<String>,
47+
/// The URL is reconstructed from a Gist ID after validation.
48+
/// It may seize to exist or change the contents, but the URL itself is guaranteed to be valid
49+
pub gh_validation_url: Option<String>,
4550
pub git_identities: Vec<String>,
4651
}
4752

@@ -202,69 +207,6 @@ impl AppConfig {
202207
None
203208
};
204209

205-
let public_name = if let Some(pub_name) = &app_args.public_name {
206-
if pub_name.is_empty() {
207-
// empty public name - make anon
208-
println!("Your Directory Profile name was removed. Your profile will be anonymous.");
209-
println!();
210-
println!(
211-
" Run `stackmuncher{} --public_name \"My Name or Nickname\"` to make it public.",
212-
EXE_SUFFIX
213-
);
214-
println!();
215-
} else {
216-
// a new public name was supplied
217-
println!(
218-
"Your new Directory Profile name: {}. It is visible to anyone, including search engines.",
219-
pub_name
220-
);
221-
println!();
222-
println!(
223-
" Run `stackmuncher{} --public_name \"\"` to remove the name and make your profile anonymous.",
224-
EXE_SUFFIX
225-
);
226-
println!();
227-
}
228-
app_args.public_name
229-
} else if app_config_cache.public_name.is_some() {
230-
app_config_cache.public_name.clone()
231-
} else {
232-
None
233-
};
234-
235-
let public_contact = if let Some(pub_contact) = &app_args.public_contact {
236-
if pub_contact.is_empty() {
237-
// no public contact details
238-
if primary_email.is_some() && !primary_email.as_ref().unwrap().is_empty() {
239-
println!("Your Directory Profile contact details were removed. Employers will be able to express their interest via Directory notifications sent to {}.", primary_email.as_ref().unwrap());
240-
} else {
241-
println!("Your Directory Profile contact details were removed. Since your primary email address is blank as well your profile will be hidden.");
242-
}
243-
244-
println!();
245-
println!(
246-
" Run `stackmuncher{} --public_contact \"Your email, website or any other contact details\"` for employers to contact you directly.",
247-
EXE_SUFFIX
248-
);
249-
println!();
250-
} else {
251-
// new public contact details
252-
println!(
253-
"Your new Directory Profile contact: {}. It is visible to anyone, including search engines.",
254-
pub_contact
255-
);
256-
println!();
257-
println!(" Run `stackmuncher{} --public_contact \"\"` to remove it.", EXE_SUFFIX);
258-
println!();
259-
}
260-
261-
app_args.public_contact
262-
} else if app_config_cache.public_contact.is_some() {
263-
app_config_cache.public_contact.clone()
264-
} else {
265-
None
266-
};
267-
268210
// print a message about multiple git IDs on the first run
269211
if config.git_identities.len() > 0 && app_args.emails.is_none() && app_config_cache.git_identities.is_empty() {
270212
println!("Only commits from {} will be analyzed. Did you use any other email addresses for Git commits in the past?",config.git_identities[0]);
@@ -293,12 +235,26 @@ impl AppConfig {
293235
println!();
294236
}
295237

238+
// GitHub login validation - use the validated URL or None if --gist param was provided
239+
// It means that the user requested a change of sorts.
240+
// Otherwise use what is in the cache without any validation.
241+
let gh_validation_url = if app_args.gh_validation_id.is_some() {
242+
// --gist was present - so something was requested
243+
match crate::configure::get_validated_gist(&app_args.gh_validation_id, &user_key_pair).await {
244+
Some(gist) => Some(gist.html_url),
245+
None => None,
246+
}
247+
} else {
248+
// --gist was not present - use what's in cache
249+
app_config_cache.gh_validation_url.clone()
250+
};
251+
296252
let app_config = AppConfig {
297253
command: app_args.command,
298254
dryrun: app_args.dryrun,
299255
primary_email,
300-
public_name,
301-
public_contact,
256+
gh_validation_id: app_args.gh_validation_id,
257+
gh_validation_url,
302258
lib_config: config,
303259
user_key_pair,
304260
config_file_path,
@@ -549,8 +505,7 @@ impl AppConfigCache {
549505
// create a blank dummy to return in case of a problem
550506
let app_config_cache = AppConfigCache {
551507
primary_email: None,
552-
public_name: None,
553-
public_contact: None,
508+
gh_validation_url: None,
554509
git_identities: Vec::new(),
555510
};
556511

@@ -602,8 +557,7 @@ impl AppConfigCache {
602557
// prepare the data to save
603558
let app_config_cache = AppConfigCache {
604559
primary_email: app_config.primary_email.clone(),
605-
public_name: app_config.public_name.clone(),
606-
public_contact: app_config.public_contact.clone(),
560+
gh_validation_url: app_config.gh_validation_url.clone(),
607561
git_identities: app_config.lib_config.git_identities.clone(),
608562
};
609563

0 commit comments

Comments
 (0)