Issues

Encryption Injection in V8

To give a little background about why Encryption injection, I need to tell you the project. We needed to use the built-in members area within Umbraco for the people who registered the website we’ve created at Front Page. It was a basic e-commerce website with a basic e-learning functionality with 4 different angles of a single pre-recorded video!

For GDPR reasons we couldn’t host those members’ details in our servers. So, it brought us to use encryption in Umbraco.

It was working great in V7 and I was curious how it’d be implemented to V8! So I’ve started working on V8 encryption injection and also think and also thought why not write!

I’ve used Surface controllers, Component Composers, an API controller, I had to learn a bit of Angular JS and I even created a package for Umbraco!

I’m going to try to go through my steps on injecting encryption to V8.

Account Surface controller

I’ve created a basic registration form. To start with, I’ve only used name, email and username fields, to align with the built-in Umbraco members’ area. We’re saving the new member to the in-built members’ table in the database.

    <div class="row">
        <div class="col-10 offset-1 text-center">
            @if (Umbraco.MemberIsLoggedOn())
            {
            <h1> Member is logged in</h1>
            }
            else
            {
            <h1>Register!</h1>
            }
        </div>
    </div>
    <div class="row no-gutters">
        <div class="col-12">
            <form method="post" action="~/umbraco/surface/AccountSurface/Register">
                <div class="form-row">
                    <div class="form-group">
                        <label for="loginName">First name</label>
                        <input type="text" name="firstName" class="form-control" id="loginName" placeholder="* Login Name">
                    </div>

                    <div class="form-group">
                        <label for="memberEmail">Email</label>
                        <input type="email" name="memberEmail" class="form-control" id="memberEmail" placeholder="* Email">
                    </div>
                    <div class="form-group">
                        <label for="pass">Password</label>
                        <input type="password" name="pass" class="form-control" id="pass" placeholder="* Password">
                    </div>
                </div>

                <p><button type="submit" class="btn btn-success"><i class="fas fa-users"></i> Create account</button></p>

            </form>
        </div>
    </div>
</div>

To do so, I’ve created an account Surface Controller. Within the controller I’ve used an encryption helper to encrypt the data we’re saving.

In my account Surface Controller, to create a member I’ve accessed MemberService by IMember newMember = Services.MemberService.CreateMember and used the CreateMember method, without using any custom fields.

    public class AccountSurfaceController : SurfaceController
    {
        [System.Web.Http.AcceptVerbs("GET", "POST")]
        [System.Web.Http.HttpPost]

        public ActionResult Register(string firstName, string memberEmail, string pass, string currentPage = "", int loginRedirect = 0)
        {
            var url = HttpContext.Request.Url;
            currentPage = url.Scheme + "://" + url.Host + ":" + url.Port + currentPage;
            if (!String.IsNullOrWhiteSpace(firstName) &&
                !String.IsNullOrWhiteSpace(memberEmail) &&
                !String.IsNullOrWhiteSpace(pass))
            {

                IMember newMember = Services.MemberService.CreateMember(firstName, memberEmail, firstName, "Member");
                newMember.Username = EncryptionHelper.Encrypt(firstName, "lower");
                newMember.Name = EncryptionHelper.Encrypt(firstName, "lower");
                newMember.Email = EncryptionHelper.Encrypt(memberEmail, "lower");
                newMember.IsApproved = true;
                Services.MemberService.Save(newMember);
                Services.MemberService.SavePassword(newMember, EncryptionHelper.Hash(pass));
                return Redirect(currentPage);
            }
            return RedirectToUmbracoPage(loginRedirect);
            
        }
}

You can also add custom fields to your registration page which I’m going to do below. With the custom fields added, we need to register them as a model to create a member. To be able to register the model, we need those custom fields to be added to the member type we’re using in the backoffice as well. I’m going to create a package for this purpose so my property editors would work with encryption when I want to edit them within Umbraco as well. Before I create the package, here’s how it will look with only textstring property editors:

@inherits Umbraco.Web.Mvc.UmbracoViewPage

  <div class="container mb-5" style="margin-top:20px">
      <div class="row">
          <div class="col-10 offset-1 text-center">
              @if (Umbraco.MemberIsLoggedOn())
              {
              <h1 class="mb-4">Already logged in!</h1>
              }
              else
              {
              <h1 class="mb-4">Register</h1>
              }
          </div>
      </div>
      <div class="row no-gutters">
          <div class="col-12">

              <form method="post" action="/umbraco/surface/AccountSurface/Register">
                  <div class="form-row">
                      <div class="form-group">
                          <label for="firstName" class="sr-only">First name</label>
                          <input type="text" name="firstName" class="form-control" id="firstName" placeholder="* First Name">
                      </div>
                      <div class="form-group">
                          <label for="lastName" class="sr-only">Last name</label>
                          <input type="text" name="lastName" class="form-control" id="lastName" placeholder="* Last Name">
                      </div>
                  </div>

                  <div class="form-group">
                      <label for="email" class="sr-only">Email</label>
                      <input type="email" name="email" class="form-control" id="email" placeholder="* Email">
                  </div>
                  <div class="form-group">
                      <label for="placeOfWork" class="sr-only">Place of work</label>
                      <input type="text" name="placeOfWork" class="form-control" id="placeOfWork" placeholder="Place of work">
                  </div>

                  <div class="form-group">
                      <label for="password" class="sr-only">Password</label>
                      <input type="password" name="password" class="form-control" id="password" placeholder="* Enter a password">
                  </div>
                  <p><button type="submit" class="btn btn-success btn-block btn-lg"><i class="fas fa-users"></i> Create account</button></p>

              </form>
          </div>

      </div>
  </div>

Here’s how the account surface controller is going to look:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using EncryptionInjection.Controllers.Helpers;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;

namespace EncryptionInjection.Controllers
{
    public class AccountSurfaceController : SurfaceController
    {
        [System.Web.Http.AcceptVerbs("GET", "POST")]
        [System.Web.Http.HttpPost]
        public ActionResult Register(string firstName, string lastName, string email, string placeOfWork, string password, string currentPage = "", int loginRedirect = 0)
        {
            var url = HttpContext.Request.Url;
            MembershipCreateStatus status = MembershipCreateStatus.UserRejected;
            currentPage = url.Scheme + "://" + url.Host + ":" + url.Port + currentPage;
            MembershipUser member = null;

            if (!String.IsNullOrWhiteSpace(email) &&
                !String.IsNullOrWhiteSpace(password) &&
                !String.IsNullOrWhiteSpace(firstName) &&
                !String.IsNullOrWhiteSpace(lastName)
            ){
                RegisterModel regModel = Members.CreateRegistrationModel("Member");
                SetPropertyValue(regModel, "firstName", EncryptionHelper.Encrypt(firstName, "camel"));
                SetPropertyValue(regModel, "lastName", EncryptionHelper.Encrypt(lastName, "camel"));
                SetPropertyValue(regModel, "placeOfWork", placeOfWork);

                regModel.Password = EncryptionHelper.Hash(password);
                regModel.Email = email;
                regModel.Name = EncryptionHelper.Encrypt(firstName + " " + lastName);
                regModel.UsernameIsEmail = false;
                regModel.Username = EncryptionHelper.Encrypt(email, "lower");


                member = Members.RegisterMember(regModel, out status, false);

            }else
            {
                return RedirectToUmbracoPage(loginRedirect);
            }

            return RedirectToUmbracoPage(loginRedirect);
        }

        public static void SetPropertyValue(RegisterModel model, string alias, object value)
        {
            model.MemberProperties.Single(p => p.Alias == alias).Value = value.ToString();
        }


    }
}

To be able to create this member in our account Surface Controller I needed to register the member as a model. There might be other ways to create a member, but I’ve picked this one because I wanted to see the difference between V7 and V8.

In both versions, create a member with only MemberService and RegisterModel. I’ve encrypted the data that’s getting saved in the members’ table of Umbraco.

You can see how the Encryption Helper class is working below. I’m going to use almost the same methods while creating EncryptionApiController as well.

using System;
using System.Configuration;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace EncryptionInjection.Controllers.Helpers
{
    public class EncryptionHelper
    {
        private static Aes getAes()
        {
            Aes aes = Aes.Create();
            aes.Key = hexStringToByteArray("9chfl69nqii761ff370kcbeuyxhliv06w2mb1q60joniq90v1n12yubo07l9dntz");
            aes.IV = hexStringToByteArray("93f56q8ximff4w6w5y1mg62dlh71m2f5");
            return aes;
        }

        public static string Encrypt(string string_data, string format = "")
        {
            Aes aes = getAes();
            string encryptionPrefix = "[[ENCRYPTED]]";

            if (String.IsNullOrWhiteSpace(string_data) || string_data.StartsWith(encryptionPrefix))
            {
                return string_data;
            }

            if (format.ToLower() == "camel")
            {
                string_data = Char.ToUpper(string_data[0]) + string_data.Substring(1).ToLower();
            }
            else if (format.ToLower() == "upper")
            {
                string_data = string_data.ToUpper();
            }
            else if (format.ToLower() == "lower")
            {
                string_data = string_data.ToLower();
            }

            string encrypted = encryptionPrefix + EncryptStringToBytes_Aes(string_data, aes.Key, aes.IV);
            return encrypted;
        }

        public static string Decrypt(string string_data)
        {
            Aes aes = getAes();
            string encryptionPrefix = "[[ENCRYPTED]]";
            if (string_data.StartsWith(encryptionPrefix))
            {
                string_data = string_data.Replace(encryptionPrefix, "");
                string roundtrip = DecryptStringFromBytes_Aes(hexStringToByteArray(string_data), aes.Key, aes.IV);
                return roundtrip;
            }

            return string_data;
        }

        private static string ByteArrayToHexString(byte[] data)
        {
            StringBuilder hex = new StringBuilder(data.Length * 2);
            foreach (byte b in data)
                hex.AppendFormat("{0:x2}", b);
            return hex.ToString();
        }

        private static byte[] hexStringToByteArray(string hex)
        {
            if (hex.Length % 2 == 1)
                throw new Exception("The binary key cannot have an odd number of digits");

            byte[] arr = new byte[hex.Length >> 1];

            for (int i = 0; i < (hex.Length) >> 1; ++i)
            {
                arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
            }

            return arr;
        }

        private static int GetHexVal(char hex)
        {
            int val = (int)hex;
            //For uppercase A-F letters:
            //return val - (val < 58 ? 48 : 55);
            //For lowercase a-f letters:
            //return val - (val < 58 ? 48 : 87);
            //Or the two combined, but a bit slower:
            return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
        }

        private static string EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encrypted;

            Aes aes = getAes();
            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

            // Create the streams used for encryption.
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(plainText);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }

            // Return the encrypted bytes from the memory stream.
            return ByteArrayToHexString(encrypted);
        }

        private static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create a decrytor to perform the stream transform.
            Aes aes = getAes();
            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

            // Create the streams used for decryption.
            using (MemoryStream msDecrypt = new MemoryStream(cipherText))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                        // Read the decrypted bytes from the decrypting stream
                        // and place them in a string.
                        plaintext = srDecrypt.ReadToEnd();
                    }
                }
            }
            return plaintext;
        }

        public static string Hash(string password)
        {
            string hashPrefix = "[[HASHED]]";
            string savedPasswordHash = "";
            string salt = "SA_LT";

            if (password.StartsWith(hashPrefix))
            {
                return password;
            }

            // prepend stored salt to entered pw
            string combo = salt.Trim() + password.Trim();

            // get data as byte array 
            var data = Encoding.ASCII.GetBytes(combo);

            using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider())
            {
                // hash
                var shadata = sha256.ComputeHash(data);

                // convert to string and remove any trailing whitespace
                savedPasswordHash = ByteArrayToHexString(shadata);
            }

            return hashPrefix + savedPasswordHash;
        }
    }
}

Everything is getting encrypted by using 64 characters of a key, and a 32 character IV. I’ve created this key and IV using Paul Seal’s password generator with only lowercase letters and numbers. You can find the link for the password generator here. There are so many methods for encryption that you can find online – this method is the one we’re currently using at Front Page agency.

I managed to encrypt our data and save it to the database. However, I might also want to create a member within Umbraco; to do so I’ve created a member Composer by using V8’s Component Composers.

Component Composers aka Dependency Injection

I’ve added custom fields to our registration form – now we can see them in the members’ area. But what about creating a member from members’ area? It should also be encrypted now, right?

I’ve created Component Composers and used MemberService without creating a custom service. My purpose for doing this is to encrypt built-in member details while creating a new member.Whenever an edit occurs with the existing member, this Composer Component will work to encrypt any edits as well.

I’ve extended the Member type and added some custom properties previously. Even though my Component Composer can see those extra properties, I couldn’t manage to retrieve the data I’m writing on them. Nik Rimmington and I tried to find the reason for this and we couldn’t find it.

Both Saving and Saved events are raised 4 times each and they both populated the custom fields on the last rise but the values are always NULL in them. I still can create the member successfully without those custom fields and then I can add them after creating the member, with editing it.

When we did this encryption Injection on V7 we used ApplicationEventHandler. But when I searched for it in Documentation, I came across the link below: https://our.umbraco.com/Documentation/Tutorials/Porting-Packages-V8/.

So, I created my own ComponentComposer. It works exactly the same with ApplicationEventHandler only it’s a lot faster and less hassle.

Here’s what my Component Composer looks like:

namespace EncryptionInjection.Composing
{
    public class MembersComposer : ComponentComposer<MembersComponent> , IUserComposer
    {
    }

    public class MembersComponent : IComponent
    {
        public void Initialize()
        {
            MemberService.Saving += this.MemberService_Saving;
            //MemberService.Saved += this.MemberService_Saved;
        }

        public void Terminate()
        {
        }

        
        private void MemberService_Saving(IMemberService sender, SaveEventArgs<IMember> e)
        {
            foreach (var entity in e.SavedEntities)
            {
                entity.Email = EncryptionHelper.Encrypt(entity.Email, "lower");
                entity.Name = EncryptionHelper.Encrypt(entity.Name);
                entity.Username = EncryptionHelper.Encrypt(entity.Username, "lower");
            }
            
        }

    }
}

At this point, we can create members from backoffice and we can edit the existing members as well. Even when we write plaintext into the built-in fields, the data will be encrypted, then saved to the database.

Because I couldn’t reach the custom fields within my Component Composer to encrypt them, I’ve created a custom property editor with a text string field, but the field is going to encrypt whatever I put in (or hash if it’s a password field). To be able to make it, I’ve created EncryptionAPIController as I mentioned above, and used it in my custom property editor.

Encryption API controller

We’re going to use this API controller to access the plaintext versions of the encrypted data. So, whoever has access to the backoffice and is allowed to see the members’ area will not see the list of members’ details as encrypted: they’ll see the decrypted versions.

For APIs we need to make sure the backoffice user has access to the data before allowing them encrypt/decrypt the data.

In V8, we can check if the backoffice user has access to the data really easily. After retrieving the current user, we can check which sections they have access to with AllowedSections, or use methods like; IsAdmin(), HasAccessToSensitiveData() etc.

When we want to show the data decrypted in the members’ area, we’re going to use this API controller again. If the backoffice user is not granted access they won’t see the decrypted data. They’re only going to see the return string we’ve set: "You are not authorised to access this resource."

namespace EncryptionInjection.Controllers
{
    public class EncryptionApiController : UmbracoAuthorizedApiController
    {
        private System.Collections.Specialized.NameValueCollection config = ConfigurationManager.AppSettings;
        public string access_code = "ONLY-FOR-SKRIFT";

        public static Aes getAes()
        {
            Aes aes = Aes.Create();
            aes.Key = hexStringToByteArray("9chfl69nqii761ff370kcbeuyxhliv06w2mb1q60joniq90v1n12yubo07l9dntz");
            aes.IV = hexStringToByteArray("93f56q8ximff4w6w5y1mg62dlh71m2f5");
            return aes;
        }

        [System.Web.Http.AcceptVerbs("GET", "POST")]
        [System.Web.Http.HttpGet]
        public string Hash(string pw, string password, string salt)
        {
            string hashPrefix = "[[HASHED]]";
            string savedPasswordHash = "";

            if (password.StartsWith(hashPrefix))
            {
                return password;
            }

            if (pw == access_code)
            {
                string combo = salt.Trim() + password.Trim();

                var data = Encoding.ASCII.GetBytes(combo);

                using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider())
                {
                    var shadata = sha256.ComputeHash(data);

                   
                    savedPasswordHash = ByteArrayToHexString(shadata);
                }

                return hashPrefix + savedPasswordHash;
            }
            else
            {
                return "";
            }
        }

        [System.Web.Http.AcceptVerbs("GET", "POST")]
        [System.Web.Http.HttpGet]
        public string Encrypt(string pw, string string_data, string format = "")
        {
            if (pw == access_code)
            {
                Aes aes = getAes();
                string encryptionPrefix = "[[ENCRYPTED]]";

                if (string_data.StartsWith(encryptionPrefix) || String.IsNullOrWhiteSpace(string_data))
                {
                    return string_data;
                }

                if (format == "camel")
                {
                    string_data = Char.ToUpper(string_data[0]) + string_data.Substring(1).ToLower();
                }
                else if (format == "upper")
                {
                    string_data = string_data.ToUpper();
                }
                else if (format == "lower")
                {
                    string_data = string_data.ToLower();
                }

                string encrypted = encryptionPrefix + EncryptStringToBytes_Aes(string_data, aes.Key, aes.IV);
                return encrypted;
            }
            else
            {
                return "";
            }
        }

        [System.Web.Http.AcceptVerbs("GET", "POST")]
        [System.Web.Http.HttpGet]
        public string Decrypt(string pw, string string_data)
        {
            if (pw == access_code)
            {
                Aes aes = getAes();
                string encryptionPrefix = "[[ENCRYPTED]]";
                string_data = string_data.Replace(encryptionPrefix, "");
                string roundtrip = DecryptStringFromBytes_Aes(hexStringToByteArray(string_data), aes.Key, aes.IV);
                return roundtrip;
            }
            else
            {
                return "";
            }
        }
        static string ByteArrayToHexString(byte[] data)
        {
            StringBuilder hex = new StringBuilder(data.Length * 2);
            foreach (byte b in data)
                hex.AppendFormat("{0:x2}", b);
            return hex.ToString();
        }

        public static byte[] hexStringToByteArray(string hex)
        {
            if (hex.Length % 2 == 1)
                throw new Exception("The binary key cannot have an odd number of digits");

            byte[] arr = new byte[hex.Length >> 1];

            for (int i = 0; i < hex.Length >> 1; ++i)
            {
                arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
            }

            return arr;
        }

        public static int GetHexVal(char hex)
        {
            int val = (int)hex;
            return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
        }

        public string EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
        {

            var authTicket = new System.Web.HttpContextWrapper(System.Web.HttpContext.Current).GetUmbracoAuthTicket();
            if (authTicket != null)
            {
                var currentUser = Services.UserService.GetByUsername(authTicket.Identity.Name);
                bool accesses = currentUser.IsAdmin();

                if (accesses)
                {
                    if (plainText == null || plainText.Length <= 0)
                        throw new ArgumentNullException("plainText");
                    if (Key == null || Key.Length <= 0)
                        throw new ArgumentNullException("Key");
                    if (IV == null || IV.Length <= 0)
                        throw new ArgumentNullException("IV");
                    byte[] encrypted;

                    Aes aes = getAes();
                    ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

                    using (MemoryStream msEncrypt = new MemoryStream())
                    {
                        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                        {
                            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                            {
                                swEncrypt.Write(plainText);
                            }
                            encrypted = msEncrypt.ToArray();
                        }
                    }

                    return ByteArrayToHexString(encrypted);
                }
                else
                {
                    return "You are not authorised to access this resource.";
                }

            }

            return "You are not authorised to access this resource.";
        }

        public string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            var authTicket = new System.Web.HttpContextWrapper(System.Web.HttpContext.Current).GetUmbracoAuthTicket();
            if (authTicket != null)
            {
                var currentUser = Services.UserService.GetByUsername(authTicket.Identity.Name);
                bool accesses = currentUser.IsAdmin() || currentUser.HasAccessToSensitiveData();

                if (accesses)
                {
                    if (cipherText == null || cipherText.Length <= 0)
                        throw new ArgumentNullException("cipherText");
                    if (Key == null || Key.Length <= 0)
                        throw new ArgumentNullException("Key");
                    if (IV == null || IV.Length <= 0)
                        throw new ArgumentNullException("IV");

                    string plaintext = null;
                    Aes aes = getAes();
                    ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

                    using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                    {
                        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                        {
                            using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                            {
                                plaintext = srDecrypt.ReadToEnd();
                            }
                        }
                    }

                    return plaintext;
                }
                else
                {
                    return "You are not authorised to access this resource.";
                }
            }

            return "You are not authorised to access this resource.";
        }
    }
}

Encrypted Property Editor Package

I’ve added custom fields to the Member type. We can save values to them with the registration page. But when we edit them within backoffice, it does not get encrypted yet as they can only save textstrings. We do need to get those values to encrypt before saving again. They’re not reachable for the creation either yet so we again need to edit the existing member, add those custom fields, then save it as encrypted.

The package will show the encrypted data which in the database as decrypted on the backoffice, and when we change the data, the property editor will encrypt it behind the scenes before saving as encrypted again to the database.

We’ll be able to create a password field with the hash method we’re using in the Encryption API controller as well. We’ll need to provide a salt for this hashed field, and we won’t be able to see the plain text within the back office.

You can find the Encrypted Property Editor Package here.

After downloading the package, we need to create our own data type. To do so, just click to create a new data type in the settings area, give it a name and pick ‘encrypted’ from the property editor drop down menu.

If you want it to be hashed, then just check the hash? check box.

Go back to the member type and change the custom fields property editors to the encrypted property editor that you’ve just created. This package allows us to see the decrypted data when we want to edit the custom field values, and when we edit it and hit save, it is encrypting the value for us before saving it.

Injection to umbraco.controller.js

All we did so far was to protect the members’ data in the database. But we still see all the data as encrypted in the back office. Content managers will need to access/edit the members’ details whenever they need, and this would be impossible if the data is shown in encrypted format.

We already have an API controller, so we can use this to decrypt the data and allow it to be seen in the backoffice. To be able to do this, we need to inject our API call to memberEditController in umbraco.controller.js (~/Umbraco/Js/umbraco.controller.js).

Right now, it’s enough to decrypt the name, user name and email fields as the other custom fields are getting decrypted with the encrypted property editor package already.

…
            if (id && id.length < 9) {
                entityResource.getById(id, 'Member').then(function (entity) {
                    $location.path('/member/member/edit/' + entity.key);
                });
            } else {
                //we are editing so get the content item from the server
                memberResource.getByKey(id).then(function (data) {

                    var e = data;
                    if (e.username.substr(0, 13) == "[[ENCRYPTED]]") {
                        $http({
                            method: 'GET',
                            url: "/Umbraco/Api/EncryptionApi/Decrypt?pw=ONLY-FOR-SKRIFT&string_data=" +
                                e.username,
                            headers: { 'Content-Type': undefined },
                            data: {}
                        }).then(function (response) {
                            if (response) {
                                e.username = response.data.replace(/"/g, '');
                                e.tabs.forEach(function (tab) {
                                    if (tab.label == "Properties") {
                                        tab.properties.forEach(function (property) {
                                            if (property.alias == "_umb_login") {
                                                property.value = e.username;
                                            }
                                        });
                                    }
                                });
                            } else {
                                return false;
                            }
                        });

                    }
                    if (e.name.substr(0, 13) == "[[ENCRYPTED]]") {
                        $http({
                            method: 'GET',
                            url: "/Umbraco/Api/EncryptionApi/Decrypt?pw=ONLY-FOR-SKRIFT&string_data=" +
                                e.name,
                            headers: { 'Content-Type': undefined },
                            data: {}
                        }).then(function (response) {
                            if (response) {
                                e.name = response.data.replace(/"/g, '');

                            } else {
                                return false;
                            }
                        });

                    }
                    if (e.email.substr(0, 13) == "[[ENCRYPTED]]") {
                        $http({
                            method: 'GET',
                            url: "/Umbraco/Api/EncryptionApi/Decrypt?pw=ONLY-FOR-SKRIFT&string_data=" +
                                e.email,
                            headers: { 'Content-Type': undefined },
                            data: {}
                        }).then(function (response) {
                            if (response) {
                                e.email = response.data.replace(/"/g, '');

                                e.tabs.forEach(function (tab) {
                                    if (tab.label == "Properties") {
                                        tab.properties.forEach(function (property) {
                                            if (property.alias == "_umb_email") {
                                                property.value = e.email;
                                            }
                                        });
                                    }
                                });

                            } else {
                                return false;
                            }
                        });

                    }
//This is not the whole method. I just wanted to show where exactly I did the injection.
…

We’ve covered both sides here – because we’ve already added Composer Components, after editing the data when we hit save it will be saved as encrypted.

All the members that have been saved to the database can be listed using a function called listViewController for PropertyEditors section in the umbraco.controller.js. All the members’ details are encrypted, and we still see the encrypted versions when we look at the members area. I’ll now inject my API calls in here to see the decrypted list of members, without touching the database! In the members’ area there is also built-in search functionality. This functionality gets the query and does the search in the database. Because our data is encrypted, the search query must be encrypted as well, otherwise the search won’t come back with any results. To do so we need to encrypt the value we write in the search field to find what we are looking for. I’m going to inject this into the same listViewController as well.

…
listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);

            if ($scope.options.filter != "" && $scope.entityType == "member") {
                $http({
                    method: 'GET',
                    url: "/Umbraco/Api/EncryptionApi/Encrypt?pw=ONLY-FOR-SKRIFT&string_data=" + $scope.options.filter,
                    headers: { 'Content-Type': undefined },
                    data: {}
                }).then(function (response) {
                    if (response) {
                        $scope.options.filter = response.data.replace(/"/g, '').replace('[[ENCRYPTED]]', '');
                        RunSearch();
                    } else {
                        return false;
                    }
                })
            } else {
                RunSearch();
            }
            function RunSearch() {
                getListResultsCallback(id, $scope.options).then(function (data) {
                    $scope.actionInProgress = false;
                    $scope.listViewResultSet = data;
                    //update all values for display
                    var section = appState.getSectionState('currentSection');
                    if ($scope.listViewResultSet.items) {
                        _.each($scope.listViewResultSet.items,
                            function (e, index) {
                                // create the folders collection (only for media list views)
                                if (section === 'media' && !mediaHelper.hasFilePropertyType(e)) {
                                    $scope.folders.push(e);
                                }
                                if (section == "member" && e.username.substr(0, 13) == "[[ENCRYPTED]]") {
                                    $http({
                                        method: 'GET',
                                        url: "/Umbraco/Api/EncryptionApi/Decrypt?pw=ONLY-FOR-SKRIFT&string_data=" + e.username,
                                        // If using Angular version <1.3, use Content-Type: false.
                                        // Otherwise, use Content-Type: undefined
                                        headers: { 'Content-Type': undefined },
                                        data: {}
                                    }).then(function (response) {
                                        if (response) {
                                            e.username = response.data.replace(/"/g, '');
                                        } else {
                                            return false;
                                        }
                                    });
                                }

                                if (section == "member" && e.name.substr(0, 13) == "[[ENCRYPTED]]") {
                                    $http({
                                        method: 'GET',
                                        url: "/Umbraco/Api/EncryptionApi/Decrypt?pw=ONLY-FOR-SKRIFT&string_data=" + e.name,
                                        // If using Angular version <1.3, use Content-Type: false.
                                        // Otherwise, use Content-Type: undefined
                                        headers: { 'Content-Type': undefined },
                                        data: {}
                                    }).then(function (response) {
                                        if (response) {
                                            e.name = response.data.replace(/"/g, '');
                                        } else {
                                            return false;
                                        }
                                    });
                                }

                                if (section == "member" && e.email.substr(0, 13) == "[[ENCRYPTED]]") {
                                    $http({
                                        method: 'GET',
                                        url: "/Umbraco/Api/EncryptionApi/Decrypt?pw=ONLY-FOR-SKRIFT&string_data=" + e.email,
                                        // If using Angular version <1.3, use Content-Type: false.
                                        // Otherwise, use Content-Type: undefined
                                        headers: { 'Content-Type': undefined },
                                        data: {}
                                    }).then(function (response) {
                                        if (response) {
                                            e.email = response.data.replace(/"/g, '');
                                        } else {
                                            return false;
                                        }
                                    });
                                }

                                setPropertyValues(e);

                            });
                    }
//This is not the whole method. I just wanted to show where exactly I did the injection.
…

They asked me to keep this article 1500-2000 words and now I see it's actually impossible! I've really enjoyed working this project and writing about it, releasing a package for it... Thank you so much for the chance of writing for Skrift!

Busra Parnell

Busra is currently working at Front Page digital agency based in Glasgow and also a Documentation Curator Intern at Umbraco HQ! Originally Turkish, she was a mathematics teacher before started a career in development. Now she loves to code in really loud metal music and is enthusiastic to learn anything! She loves to cook and eat, swim, hike, dance! Loud laugher.

comments powered by Disqus