Created
January 12, 2019 22:01
-
-
Save guitarrapc/dab9536831ca39c44d90264f2e62ff73 to your computer and use it in GitHub Desktop.
Convert TeamsId's export csv to Bitwarden's import json format.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"folders": [ | |
{ | |
"id": "fa2a6822-8595-44e3-a845-269cc3201c65", | |
"name": "group1" | |
} | |
], | |
"items": [] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void Main() | |
{ | |
var outputPath = $@"./bitwarden_import/bitwarden_importdata{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.json"; | |
var teamsidPersonalCsv = @"./teamsid_export/indivisual_records_2019-01-12 20_08_36.csv"; | |
var teamsidOrganizationCsv = @"./teamsid_export/Organization_records_2019-01-12 21_20_10.csv"; | |
var teamsidCsvs = new[] { teamsidPersonalCsv, teamsidOrganizationCsv }; | |
var bitwardenFolderDefinitionJson = @"./bitwarden_export/bitwarden_export_folder_definition.json"; | |
// deserialize folder definitions | |
BitwardenFolderDefinition DeserializeFolderJson(string path) | |
{ | |
using (var stream = File.OpenRead(path)) | |
{ | |
var folders = Utf8Json.JsonSerializer.Deserialize<BitwardenFolderDefinition>(stream); | |
return folders; | |
} | |
} | |
var folderDefinition = DeserializeFolderJson(bitwardenFolderDefinitionJson); | |
// convert teamsid to bitwarden | |
var combinedResult = teamsidCsvs.SelectMany(json => | |
{ | |
var parser = new CsvParser(json); | |
var teamsIdDatas = parser.Parse<TeamsId>(); | |
var bitwardenItems = new BitwardenConverter().Convert(teamsIdDatas, folderDefinition, "Socials"); | |
return bitwardenItems; | |
}) | |
.ToArray(); | |
combinedResult.Dump(); | |
// serialize bitwarden import data | |
var importData = new BitwardenDefinition | |
{ | |
folders = folderDefinition.folders, | |
items = combinedResult, | |
}; | |
var jsonBytes = Utf8Json.JsonSerializer.Serialize<BitwardenDefinition>(importData); | |
// output json | |
File.WriteAllBytes(outputPath, jsonBytes); | |
} | |
public class BitwardenConverter | |
{ | |
private class FieldRecord | |
{ | |
public string Url { get; set; } | |
public string Email { get; set; } | |
public string UserName { get; set; } | |
public string Password { get; set; } | |
public (string key, string value)[] SecureFields { get; set; } | |
public (string key, string value)[] Fields { get; set; } | |
public string Group { get; set; } | |
} | |
public BitwardenItem[] Convert(TeamsId[] from, BitwardenFolderDefinition folderDefinition, string defaultGroup = null) | |
{ | |
var records = from | |
.Select(record => | |
{ | |
// reflection to check fieldXX and get valueXX | |
var t = record.GetType(); | |
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); | |
var description = GetPropertyValue(record, t, "description"); | |
var note = GetPropertyValue(record, t, "note"); | |
var fieldRecord = ParseFieldRecord(props, t, record, defaultGroup); | |
// generate typed item | |
var bitwarden = new BitwardenItem | |
{ | |
id = Guid.NewGuid().ToString(), | |
organizationId = null, | |
collectionIds = null, | |
type = 1, | |
name = description, | |
login = new BitwardenLogin | |
{ | |
username = fieldRecord.UserName, | |
password = fieldRecord.Password, | |
uris = new[] { | |
new BitwardenUri | |
{ | |
uri = fieldRecord.Url, | |
match = null, | |
} | |
} | |
}, | |
favorite = false, | |
notes = note, | |
folderId = folderDefinition.GetId(fieldRecord.Group), | |
}; | |
// initialize fields | |
var customField = fieldRecord.Fields == null | |
? Array.Empty<BitwardenField>() | |
: fieldRecord.Fields.Select(x => new BitwardenField { name = x.key, value = x.value, type = 0 }); | |
var customSecretField = fieldRecord.SecureFields == null | |
? Array.Empty<BitwardenField>() | |
: fieldRecord.SecureFields.Select(x => new BitwardenField { name = x.key, value = x.value, type = 1 }); | |
var fields = customField.Concat(customSecretField).ToArray(); | |
if (fields.Any()) | |
{ | |
bitwarden.fields = fields; | |
} | |
return bitwarden; | |
}) | |
.ToArray(); | |
return records; | |
} | |
private FieldRecord ParseFieldRecord(PropertyInfo[] props, Type t, TeamsId source, string defaultGroup = null) | |
{ | |
var fieldRecord = new FieldRecord(); | |
// get FieldXX properties via reflection | |
var fieldRecords = props | |
.Where(x => Match(x.Name, @"Field\d+")) | |
.Where(x => GetPropertyValue(source, t, x.Name) != "") | |
.Select(x => (key: GetPropertyValue(source, t, x.Name), value: GetPropertyValue(source, t, x.Name.Replace("Field", "Value")))) | |
.ToArray(); | |
// get field's value and categolize each via field name regex pattern | |
var secureMemoList = new List<(string, string)>(); | |
var memoList = new List<(string, string)>(); | |
foreach (var record in fieldRecords) | |
{ | |
switch (record.key) | |
{ | |
case var _ when Match(record.key, "url") && fieldRecord.Url == null: | |
fieldRecord.Url = record.value; | |
break; | |
case var _ when Match(record.key, "email|e-mail") && fieldRecord.Email == null: | |
fieldRecord.Email = record.value; | |
break; | |
case var _ when Match(record.key, "username") && fieldRecord.UserName == null: | |
fieldRecord.UserName = record.value; | |
break; | |
case var _ when Match(record.key, "password") && fieldRecord.Password == null: | |
fieldRecord.Password = record.value; | |
break; | |
case var _ when Match(record.key, "pass|access|secret|pin|token|enctypt"): | |
secureMemoList.Add((record.key, record.value)); | |
break; | |
case var _ when Match(record.key, "group"): | |
fieldRecord.Group = record.value; | |
break; | |
default: | |
memoList.Add((record.key, record.value)); | |
break; | |
} | |
} | |
// group fallback | |
if (defaultGroup != null && string.IsNullOrWhiteSpace(fieldRecord.Group)) | |
{ | |
fieldRecord.Group = defaultGroup; | |
} | |
fieldRecord.Fields = memoList.ToArray(); | |
fieldRecord.SecureFields = secureMemoList.ToArray(); | |
return fieldRecord; | |
} | |
private bool Match(string text, string pattern) | |
{ | |
return Regex.IsMatch(text, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); | |
} | |
private string GetPropertyValue(TeamsId record, Type t, string propertyField) | |
{ | |
return (string)t.GetProperty(propertyField).GetValue(record); | |
} | |
} | |
public class CsvParser | |
{ | |
private readonly string path; | |
public CsvParser(string path) | |
{ | |
this.path = path; | |
} | |
public T[] Parse<T>() where T : class | |
{ | |
using (var reader = new StreamReader(path)) | |
using (var csv = new CsvReader(reader)) | |
{ | |
csv.Configuration.MissingFieldFound = null; | |
var records = csv.GetRecords<T>(); | |
return records.ToArray(); | |
} | |
} | |
} | |
public class BitwardenDefinition | |
{ | |
public BitwardenFolder[] folders { get; set; } | |
public BitwardenItem[] items {get;set;} | |
} | |
public class BitwardenItem | |
{ | |
public string id { get; set; } | |
public string organizationId { get; set; } | |
public string collectionIds { get; set; } | |
public bool favorite { get; set; } | |
public string folderId { get; set; } | |
// login = 1, card = 3 | |
public int type { get; set; } | |
public string name { get; set; } | |
public string notes { get; set; } | |
public BitwardenField[] fields { get; set; } | |
public BitwardenLogin login { get; set; } | |
} | |
public class BitwardenLogin | |
{ | |
public BitwardenUri[] uris { get; set; } | |
public string username { get; set; } | |
public string password { get; set; } | |
// authenticator = null (premium only) | |
public string totp { get; set; } | |
} | |
public class BitwardenUri | |
{ | |
// default = null | |
public object match { get; set; } | |
public string uri { get; set; } | |
} | |
public class BitwardenField | |
{ | |
public string name { get; set; } | |
public string value { get; set; } | |
// custom = 0, hidden = 1, boolean = 2 | |
public int type { get; set; } | |
} | |
public class BitwardenFolderDefinition | |
{ | |
public BitwardenFolder[] folders { get; set; } | |
private static readonly string defaultIdName = "Social"; | |
public string GetId(string name) | |
{ | |
if (folders == null || !folders.Any()) return defaultIdName; | |
return folders.FirstOrDefault(x => x.name == name)?.id ?? defaultIdName; | |
} | |
} | |
public class BitwardenFolder | |
{ | |
public string id { get; set; } | |
public string name { get; set; } | |
} | |
public class TeamsId | |
{ | |
public string description { get; set; } | |
public string note { get; set; } | |
public string Field0 { get; set; } | |
public string Type0 { get; set; } | |
public string Value0 { get; set; } | |
public string Field1 { get; set; } | |
public string Type1 { get; set; } | |
public string Value1 { get; set; } | |
public string Field2 { get; set; } | |
public string Type2 { get; set; } | |
public string Value2 { get; set; } | |
public string Field3 { get; set; } | |
public string Type3 { get; set; } | |
public string Value3 { get; set; } | |
public string Field4 { get; set; } | |
public string Type4 { get; set; } | |
public string Value4 { get; set; } | |
public string Field5 { get; set; } | |
public string Type5 { get; set; } | |
public string Value5 { get; set; } | |
public string Field6 { get; set; } | |
public string Type6 { get; set; } | |
public string Value6 { get; set; } | |
public string Field7 { get; set; } | |
public string Type7 { get; set; } | |
public string Value7 { get; set; } | |
public string Field8 { get; set; } | |
public string Type8 { get; set; } | |
public string Value8 { get; set; } | |
public string Field9 { get; set; } | |
public string Type9 { get; set; } | |
public string Value9 { get; set; } | |
public string Field10 { get; set; } | |
public string Type10 { get; set; } | |
public string Value10 { get; set; } | |
public string Field11 { get; set; } | |
public string Type11 { get; set; } | |
public string Value11 { get; set; } | |
public string Field12 { get; set; } | |
public string Type12 { get; set; } | |
public string Value12 { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment