• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

HicServices / RDMP / 18406657859

02 Oct 2025 03:33PM UTC coverage: 57.309% (-0.2%) from 57.474%
18406657859

push

github

web-flow
Merge pull request #2237 from HicServices/task/RDMP-334-extractability

Task/rdmp 334 extractability

11400 of 21427 branches covered (53.2%)

Branch coverage included in aggregate %.

11 of 102 new or added lines in 11 files covered. (10.78%)

64 existing lines in 8 files now uncovered.

32365 of 54940 relevant lines covered (58.91%)

8794.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

20.29
/Rdmp.Core/Repositories/Managers/PasswordEncryptionKeyLocation.cs
1
// Copyright (c) The University of Dundee 2018-2019
2
// This file is part of the Research Data Management Platform (RDMP).
3
// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4
// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5
// You should have received a copy of the GNU General Public License along with RDMP. If not, see <https://www.gnu.org/licenses/>.
6

7
using System;
8
using System.IO;
9
using System.Security.Cryptography;
10
using System.Text;
11
using Rdmp.Core.Curation;
12
using Rdmp.Core.MapsDirectlyToDatabaseTable.Injection;
13

14
namespace Rdmp.Core.Repositories.Managers;
15

16
/// <summary>
17
/// The file system location of the RSA private decryption key used to decrypt passwords stored in RDMP database.  There can only ever be one PasswordEncryptionKeyLocation
18
/// and this is used by all SimpleStringValueEncryption.  This means that passwords can be securely held in the RDMP database so long as suitable windows account management
19
/// takes place (only providing access to the key file location to users who should be able to use the passwords).
20
/// 
21
/// <para>See PasswordEncryptionKeyLocationUI for more information.</para>
22
/// </summary>
23
public class PasswordEncryptionKeyLocation : IEncryptionManager, IInjectKnown
24
{
25
    private readonly ICatalogueRepository _catalogueRepository;
26

27
    public const string RDMP_KEY_LOCATION = "RDMP_KEY_LOCATION";
28

29
    /// <summary>
30
    /// Prepares to retrieve/create the key file for the given platform database
31
    /// </summary>
32
    /// <param name="catalogueRepository"></param>
33
    public PasswordEncryptionKeyLocation(ICatalogueRepository catalogueRepository)
479✔
34
    {
35
        _catalogueRepository = catalogueRepository;
479✔
36

37
        ClearAllInjections();
479✔
38
    }
479✔
39

40
    public IEncryptStrings GetEncrypter() => new SimpleStringValueEncryption(OpenKeyFile());
5,041✔
41

42
    private Lazy<string> _knownKeyFileLocation;
43

44
    /// <summary>
45
    /// Gets the physical file path to the currently configured RSA private key for encrypting/decrypting passwords or null if no
46
    /// custom keyfile has been created yet.  The answer to this question is cached, call <see cref="ClearAllInjections"/> to reset
47
    /// the cache
48
    /// </summary>
49
    /// <returns></returns>
50
    public string GetKeyFileLocation() => _knownKeyFileLocation.Value;
5,791✔
51

52
    private string GetKeyFileLocationImpl()
53
    {
54
        // Prefer to get it from the environment variable
55
        var fromEnvVar = Environment.GetEnvironmentVariable(RDMP_KEY_LOCATION);
975✔
56

57
        return fromEnvVar ?? _catalogueRepository.GetEncryptionKeyPath();
975!
58
    }
59

60

61
    /// <summary>
62
    /// Connects to the private key location and returns the encryption/decryption parameters stored in it
63
    /// </summary>
64
    /// <returns></returns>
65
    public string OpenKeyFile()
66
    {
67
        var location = GetKeyFileLocation();
5,041✔
68
        return location is null ? null : File.ReadAllText(location);
5,041!
69
    }
70

71
    private static void DeserializeFromLocation(string keyLocation)
72
    {
73
        if (string.IsNullOrWhiteSpace(keyLocation))
×
74
            return;
×
75
        try
76
        {
77
            new RSACryptoServiceProvider().FromXmlString(File.ReadAllText(keyLocation));
×
78
        }
×
79
        catch (Exception ex)
×
80
        {
81
            throw new Exception(
×
82
                $"Failed to open and read key file {keyLocation} (possibly it is not in a shared network location or the current user ({Environment.UserName}) does not have access to the file?)",
×
83
                ex);
×
84
        }
85
    }
×
86

87
    /// <summary>
88
    /// Creates a new private RSA encryption key certificate at the given location and sets the catalogue repository to use it for encrypting passwords.
89
    /// This will make any existing serialized passwords irretrievable unless you restore and reset the original key file location.
90
    /// </summary>
91
    /// <param name="path"></param>
92
    /// <returns></returns>
93
    public FileInfo CreateNewKeyFile(string path)
94
    {
UNCOV
95
        ClearAllInjections();
×
96

UNCOV
97
        var existingKey = GetKeyFileLocation();
×
UNCOV
98
        if (existingKey != null)
×
99
            throw new NotSupportedException($"There is already a key file at location:{existingKey}");
×
100

UNCOV
101
        var provider = new RSACryptoServiceProvider(4096);
×
102

UNCOV
103
        var fi = new FileInfo(path);
×
104

UNCOV
105
        if (fi.Directory is { Exists: false })
×
106
            fi.Directory.Create();
×
107

UNCOV
108
        using (var stream = fi.Create())
×
109
        {
UNCOV
110
            stream.Write(Encoding.UTF8.GetBytes(provider.ToXmlString(true)));
×
UNCOV
111
        }
×
112

UNCOV
113
        var fileInfo = new FileInfo(path);
×
114

UNCOV
115
        if (!fileInfo.Exists)
×
116
            throw new Exception("Created file but somehow it didn't exist!?!");
×
117

UNCOV
118
        _catalogueRepository.SetEncryptionKeyPath(fileInfo.FullName);
×
119

UNCOV
120
        ClearAllInjections();
×
121

UNCOV
122
        return fileInfo;
×
123
    }
124

125
    /// <summary>
126
    /// Changes the location of the RSA private key file to a physical location on disk (which must exist)
127
    /// </summary>
128
    /// <param name="newLocation"></param>
129
    public void ChangeLocation(string newLocation)
130
    {
131
        ClearAllInjections();
×
132

133
        if (!File.Exists(newLocation))
×
134
            throw new FileNotFoundException($"Could not find key file at:{newLocation}");
×
135

136
        //confirms that it is accessible and deserializable
137
        DeserializeFromLocation(newLocation);
×
138

139
        _catalogueRepository.SetEncryptionKeyPath(newLocation);
×
140

141
        ClearAllInjections();
×
142
    }
×
143

144
    /// <summary>
145
    /// Deletes the location of the RSA private key file from the platform database (does not delete the physical
146
    /// file).
147
    /// </summary>
148
    public void DeleteKey()
149
    {
UNCOV
150
        ClearAllInjections();
×
151

UNCOV
152
        if (GetKeyFileLocation() == null)
×
UNCOV
153
            throw new NotSupportedException("Cannot delete key because there is no key file configured");
×
154

UNCOV
155
        _catalogueRepository.DeleteEncryptionKeyPath();
×
156

UNCOV
157
        ClearAllInjections();
×
UNCOV
158
    }
×
159

160
    public void ClearAllInjections()
161
    {
162
        _knownKeyFileLocation = new Lazy<string>(GetKeyFileLocationImpl);
1,225✔
163
    }
1,225✔
164
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc